Skip to content

Commit 932cdc5

Browse files
olemartinorgOle Martin Handeland
andauthored
Replacing post code API with package from npm (#3943)
Co-authored-by: Ole Martin Handeland <git@olemartin.org>
1 parent 9cf23cb commit 932cdc5

10 files changed

Lines changed: 246 additions & 105 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@
163163
"marked": "16.4.2",
164164
"marked-mangle": "1.1.12",
165165
"node-polyfill-webpack-plugin": "4.1.0",
166+
"norway-postal-codes": "^4.1.0",
166167
"react": "19.2.3",
167168
"react-compiler-webpack": "0.2.1",
168169
"react-content-loader": "7.1.1",

src/hooks/queries/usePostPlaceQuery.ts

Lines changed: 0 additions & 36 deletions
This file was deleted.

src/layout/Address/AddressComponent.test.tsx

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ import { AddressComponent } from 'src/layout/Address/AddressComponent';
88
import { renderGenericComponentTest } from 'src/test/renderWithProviders';
99
import type { RenderGenericComponentTestProps } from 'src/test/renderWithProviders';
1010

11+
jest.mock('norway-postal-codes', () => ({
12+
__esModule: true,
13+
default: {
14+
'0001': 'OSLO',
15+
'0002': 'BERGEN',
16+
'1613': 'FREDRIKSTAD',
17+
'4609': 'KARDEMOMME BY',
18+
},
19+
}));
20+
1121
const render = async ({ component, ...rest }: Partial<RenderGenericComponentTestProps<'Address'>> = {}) =>
1222
await renderGenericComponentTest({
1323
type: 'Address',
@@ -27,18 +37,6 @@ const render = async ({ component, ...rest }: Partial<RenderGenericComponentTest
2737
...component,
2838
},
2939
...rest,
30-
queries: {
31-
fetchPostPlace: async (input) => {
32-
if (input === '0001') {
33-
return { valid: true, result: 'OSLO' };
34-
}
35-
if (input === '0002') {
36-
return { valid: true, result: 'BERGEN' };
37-
}
38-
return { valid: false, result: '' };
39-
},
40-
...rest.queries,
41-
},
4240
});
4341

4442
describe('AddressComponent', () => {
@@ -113,10 +111,6 @@ describe('AddressComponent', () => {
113111
},
114112
queries: {
115113
fetchFormData: async () => ({ address: 'initial address', zipCode: '0001' }),
116-
fetchPostPlace: (zipCode: string) =>
117-
zipCode === '0001'
118-
? Promise.resolve({ valid: true, result: 'OSLO' })
119-
: Promise.resolve({ valid: false, result: '' }),
120114
},
121115
});
122116

@@ -167,7 +161,7 @@ describe('AddressComponent', () => {
167161
});
168162

169163
it('should call dispatch for post place when zip code is cleared', async () => {
170-
const { formDataMethods, queries } = await render({
164+
const { formDataMethods } = await render({
171165
queries: {
172166
fetchFormData: async () => ({ address: 'a', zipCode: '0001', postPlace: 'Oslo' }),
173167
},
@@ -187,12 +181,10 @@ describe('AddressComponent', () => {
187181
reference: { field: 'postPlace', dataType: defaultDataTypeMock },
188182
newValue: '',
189183
});
190-
191-
expect(queries.fetchPostPlace).toHaveBeenCalledTimes(1);
192184
});
193185

194-
it('should only call fetchPostPlace once at the end, when debouncing', async () => {
195-
const { queries } = await render({
186+
it('should update post place after typing zip code with debouncing', async () => {
187+
await render({
196188
queries: {
197189
fetchFormData: async () => ({ address: 'a', zipCode: '', postPlace: '' }),
198190
},
@@ -202,9 +194,6 @@ describe('AddressComponent', () => {
202194
await waitFor(() => expect(screen.getByRole('textbox', { name: 'Poststed' })).toHaveDisplayValue('BERGEN'), {
203195
timeout: 15000,
204196
});
205-
206-
expect(queries.fetchPostPlace).toHaveBeenCalledTimes(1);
207-
expect(queries.fetchPostPlace).toHaveBeenCalledWith('0002');
208197
});
209198

210199
it('should display no extra markings when required is false, and labelSettings.optionalIndicator is not true', async () => {

src/layout/Address/AddressComponent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import { ComponentValidations } from 'src/features/validation/ComponentValidatio
1414
import { useBindingValidationsFor } from 'src/features/validation/selectors/bindingValidationsForNode';
1515
import { useComponentValidationsFor } from 'src/features/validation/selectors/componentValidationsForNode';
1616
import { hasValidationErrors } from 'src/features/validation/utils';
17-
import { usePostPlaceQuery } from 'src/hooks/queries/usePostPlaceQuery';
1817
import { useOurEffectEvent } from 'src/hooks/useOurEffectEvent';
1918
import classes from 'src/layout/Address/AddressComponent.module.css';
19+
import { usePostPlace } from 'src/layout/Address/usePostPlace';
2020
import { useItemWhenType } from 'src/utils/layout/useNodeItem';
2121
import type { PropsFromGenericComponent } from 'src/layout';
2222
import type { IDataModelBindingsForAddress } from 'src/layout/Address/config.generated';
@@ -56,7 +56,7 @@ export function AddressComponent({ baseComponentId }: PropsFromGenericComponent<
5656

5757
const zipCodeDebounced = FD.useDebouncedPick(dataModelBindings.zipCode);
5858
const slowZip = typeof zipCodeDebounced === 'string' ? zipCodeDebounced : undefined;
59-
const postPlaceQueryData = usePostPlaceQuery(slowZip, !hasValidationErrors(bindingValidations?.zipCode));
59+
const postPlaceQueryData = usePostPlace(slowZip, !hasValidationErrors(bindingValidations?.zipCode));
6060
useEffect(() => updatePostPlace(postPlaceQueryData), [postPlaceQueryData, updatePostPlace]);
6161

6262
return (

src/layout/Address/usePostPlace.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import postalCodes from 'norway-postal-codes';
2+
3+
const __default__ = '';
4+
5+
/**
6+
* Looks up the post place for a given zip code using the norway-postal-codes package.
7+
* This hook was designed primarily for use in the Address component.
8+
*/
9+
export function usePostPlace(zipCode: string | undefined, enabled: boolean) {
10+
const _enabled = enabled && Boolean(zipCode?.length) && zipCode !== __default__ && zipCode !== '0';
11+
12+
if (!_enabled) {
13+
return __default__;
14+
}
15+
16+
const postPlace = postalCodes[zipCode!];
17+
return postPlace ?? __default__;
18+
}

src/queries/queries.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -372,14 +372,6 @@ export const fetchLayoutSchema = async (): Promise<JSONSchema7 | undefined> => {
372372
return (await axios.get(`${schemaBaseUrl}${LAYOUT_SCHEMA_NAME}`)).data ?? undefined;
373373
};
374374

375-
export const fetchPostPlace = (zipCode: string): Promise<{ result: string; valid: boolean }> =>
376-
httpGet('https://api.bring.com/shippingguide/api/postalCode.json', {
377-
params: {
378-
clientUrl: window.location.href,
379-
pnr: zipCode,
380-
},
381-
});
382-
383375
export function fetchExternalApi({
384376
instanceId,
385377
externalApiId,

src/test/renderWithProviders.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,6 @@ const defaultQueryMocks: AppQueries = {
149149
fetchTextResources: async (language) => ({ language, resources: getTextResourcesMock() }),
150150
fetchLayoutSchema: async () => ({}) as JSONSchema7,
151151
fetchAppLanguages: async () => [{ language: 'nb' }, { language: 'nn' }, { language: 'en' }],
152-
fetchPostPlace: async () => ({ valid: true, result: 'OSLO' }),
153152
fetchLayoutSettings: async () => ({ pages: { order: [] } }),
154153
fetchLayouts: () => Promise.reject(new Error('fetchLayouts not mocked')),
155154
fetchLayoutsForInstance: () => Promise.reject(new Error('fetchLayoutsForInstance not mocked')),

test/e2e/integration/frontend-test/components.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -270,25 +270,11 @@ describe('UI Components', () => {
270270
it('address component fetches post place from zip code', () => {
271271
cy.goto('changename');
272272

273-
// Mock zip code API, so that we don't rely on external services for our tests
274-
cy.intercept('GET', 'https://api.bring.com/shippingguide/api/postalCode.json**', (req) => {
275-
req.reply((res) => {
276-
res.send({
277-
body: {
278-
postalCodeType: 'NORMAL',
279-
result: 'KARDEMOMME BY', // Intentionally wrong, to test that our mock is used
280-
valid: true,
281-
},
282-
});
283-
});
284-
}).as('zipCodeApi');
285-
286273
cy.get(appFrontend.changeOfName.address.street_name).type('Sesame Street 1A');
287274
cy.get(appFrontend.changeOfName.address.street_name).blur();
288-
cy.get(appFrontend.changeOfName.address.zip_code).type('0123');
275+
cy.get(appFrontend.changeOfName.address.zip_code).type('4609');
289276
cy.get(appFrontend.changeOfName.address.zip_code).blur();
290277
cy.get(appFrontend.changeOfName.address.post_place).should('have.value', 'KARDEMOMME BY');
291-
cy.get('@zipCodeApi').its('request.url').should('include', '0123');
292278
});
293279

294280
it('should not be possible to check a readonly checkbox', () => {

test/e2e/integration/multiple-datamodels-test/saving.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,6 @@ describe('saving multiple data models', () => {
1515
});
1616

1717
it('Calls save on individual data models', () => {
18-
cy.intercept('GET', 'https://api.bring.com/shippingguide/api/postalCode.json**', (req) => {
19-
req.reply((res) => {
20-
res.send({
21-
body: {
22-
postalCodeType: 'NORMAL',
23-
result: 'BARTEBYEN', // Intentionally wrong, to test that our mock is used
24-
valid: true,
25-
},
26-
});
27-
});
28-
}).as('zipCodeApi');
29-
3018
cy.waitUntilSaved();
3119
cy.get('@saveFormData.all').should('have.length', 1); // PreselectedOptionIndex
3220

@@ -49,14 +37,11 @@ describe('saving multiple data models', () => {
4937
cy.waitUntilSaved();
5038
cy.get('@saveFormData.all').should('have.length', 3);
5139

52-
cy.findByRole('textbox', { name: /postnr/i }).type('7010');
53-
cy.findByRole('textbox', { name: /poststed/i }).should('have.value', 'BARTEBYEN');
40+
cy.findByRole('textbox', { name: /postnr/i }).type('4609');
41+
cy.findByRole('textbox', { name: /poststed/i }).should('have.value', 'KARDEMOMME BY');
5442

55-
// The number of requests we get here depends on how long time it took for the post code API request to get back
56-
// to us and store the data in the data model. It will be 4 (when it was fast) or 5 (when it was slow).
5743
cy.waitUntilSaved();
58-
cy.get('@saveFormData.all').should('have.length.at.least', 4);
59-
cy.get('@saveFormData.all').should('have.length.at.most', 5);
44+
cy.get('@saveFormData.all').should('have.length', 4);
6045
cy.get('@saveFormData.all').should(haveTheSameUrls);
6146

6247
cy.get(appFrontend.altinnError).should('not.exist');

0 commit comments

Comments
 (0)