Skip to content

Commit 9e3219c

Browse files
committed
test(rest): lock export header row to request locale
Regression test for the export header localization: a zh request yields 'ID,标题,完成' and the default-locale request yields 'ID,Title,Done' (the bundle's labels, never the raw schema 'RawTitle'/'RawDone'), proving the header tracks Accept-Language / ?locale= via translateMetaItem.
1 parent 8fda00b commit 9e3219c

1 file changed

Lines changed: 63 additions & 0 deletions

File tree

packages/rest/src/rest.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,69 @@ describe('RestServer', () => {
10631063
// First match wins → the more-specific export route must come first.
10641064
expect(exportIdx).toBeLessThan(getByIdIdx);
10651065
});
1066+
1067+
// Regression: the header row must carry the same localized field labels the
1068+
// UI renders (Accept-Language / ?locale=), not the raw `field.label` from the
1069+
// object schema. The route translates the schema via `translateMetaItem`
1070+
// before building the header, so a locale switch flips the header language.
1071+
it('localizes the export header row to the request locale', async () => {
1072+
// Schema field labels are deliberately distinct from the bundle so a
1073+
// green assertion proves the bundle was applied (not the raw labels).
1074+
const NAMED_SCHEMA = {
1075+
name: 'task',
1076+
fields: [
1077+
{ name: 'id', type: 'text', label: 'ID' },
1078+
{ name: 'title', type: 'text', label: 'RawTitle' },
1079+
{ name: 'done', type: 'boolean', label: 'RawDone' },
1080+
],
1081+
};
1082+
const bundle: Record<string, any> = {
1083+
en: { objects: { task: { fields: { title: { label: 'Title' }, done: { label: 'Done' } } } } },
1084+
zh: { objects: { task: { fields: { title: { label: '标题' }, done: { label: '完成' } } } } },
1085+
};
1086+
const i18n = {
1087+
getLocales: () => ['en', 'zh'],
1088+
getTranslations: (l: string) => bundle[l],
1089+
getDefaultLocale: () => 'en',
1090+
};
1091+
1092+
const p: any = createMockProtocol();
1093+
p.getMetaItem = vi.fn().mockResolvedValue({ type: 'object', name: 'task', item: NAMED_SCHEMA });
1094+
// First page returns one row, subsequent pages are empty (ends the stream).
1095+
p.findData = vi.fn(async ({ query }: any) =>
1096+
(query?.$skip ?? 0) === 0 ? { data: [{ id: '1', title: 'x', done: true }] } : { data: [] },
1097+
);
1098+
1099+
// i18nServiceProvider is the 14th constructor arg (after server, protocol,
1100+
// config, and the 10 service providers preceding it).
1101+
const rest = new RestServer(
1102+
server as any, p as any, {},
1103+
undefined, undefined, undefined, undefined, undefined,
1104+
undefined, undefined, undefined, undefined, undefined,
1105+
async () => i18n as any,
1106+
);
1107+
rest.registerRoutes();
1108+
const route = getExportRoute(rest);
1109+
1110+
// locale=zh → Chinese header; `id` has no override so it keeps its label.
1111+
const zh = makeRes();
1112+
await route!.handler({
1113+
params: { object: 'task' },
1114+
query: { format: 'csv', fields: 'id,title,done', locale: 'zh' },
1115+
headers: {},
1116+
} as any, zh.res);
1117+
expect(zh.chunks.join('').split('\r\n')[0]).toBe('ID,标题,完成');
1118+
1119+
// Default locale (en, no ?locale=) → English header from the bundle,
1120+
// i.e. 'Title'/'Done', never the raw 'RawTitle'/'RawDone'.
1121+
const en = makeRes();
1122+
await route!.handler({
1123+
params: { object: 'task' },
1124+
query: { format: 'csv', fields: 'id,title,done' },
1125+
headers: {},
1126+
} as any, en.res);
1127+
expect(en.chunks.join('').split('\r\n')[0]).toBe('ID,Title,Done');
1128+
});
10661129
});
10671130

10681131
// -----------------------------------------------------------------------

0 commit comments

Comments
 (0)