test: Add text families for property-based tests (#38511)

This commit is contained in:
Benny Neugebauer 2026-09-18 07:17:54 +00:00 committed by GitHub
parent 82f3a0154b
commit 87ac2ef5b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 118 additions and 19 deletions

View File

@ -1,3 +1,4 @@
import fc from 'fast-check';
import type { MockProxy } from 'vitest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { IExecuteFunctions, INode, INodeParameterResourceLocator } from 'n8n-workflow';
@ -23,6 +24,7 @@ import {
} from '../shared/GenericFunctions';
import { versionDescription as versionDescriptionV1 } from '../v1/VersionDescription';
import { versionDescription as versionDescriptionV2 } from '../v2/VersionDescription';
import { wordFrom } from '@test/text-families';
import type { Mock } from 'vitest';
const collectNotionUrlExpressions = (value: unknown): string[] => {
@ -603,6 +605,15 @@ describe('Test Notion, simplifyObjects', () => {
},
});
const keyFor = (propertyName: string, nodeVersion: number, text = 'x') => {
const [result] = simplifyObjects(
[page({ [propertyName]: richText(text) })],
false,
nodeVersion,
);
return Object.keys(result).find((key) => result[key] === text);
};
describe('v3 keeps change-case v5 Unicode-aware keys', () => {
it('preserves non-ASCII characters in simplified property keys', () => {
const result = simplifyObjects([page({ Prénom: richText('Jean') })], false, 3);
@ -622,6 +633,14 @@ describe('Test Notion, simplifyObjects', () => {
expect(result[0]).toHaveProperty(expectedKey, 'x');
});
it('keeps any accented word as its key', () => {
fc.assert(
fc.property(wordFrom('latin-accented'), (name) => {
expect(keyFor(name, 3)).toBe(`property_${name}`);
}),
);
});
});
describe('earlier versions fold to the pre-v5 ASCII shape', () => {
@ -663,5 +682,13 @@ describe('Test Notion, simplifyObjects', () => {
expect(result[0]).toHaveProperty(expectedKey, 'x');
});
it('folds any accented word to an ASCII key', () => {
fc.assert(
fc.property(wordFrom('latin-accented'), (name) => {
expect(keyFor(name, 2)).toMatch(/^property_[a-z_]*$/);
}),
);
});
});
});

View File

@ -926,6 +926,9 @@
"@types/ssh2-sftp-client": "^9.0.5",
"@types/uuid": "catalog:",
"@types/xml2js": "catalog:",
"@unicode/unicode-18.0.0": "2.0.7",
"confusables": "1.1.1",
"fast-check": "catalog:",
"n8n-containers": "workspace:*",
"n8n-core": "workspace:*",
"nock": "catalog:",

View File

@ -0,0 +1,60 @@
import bidiControl from '@unicode/unicode-18.0.0/Binary_Property/Bidi_Control/symbols';
import changesWhenCasemapped from '@unicode/unicode-18.0.0/Binary_Property/Changes_When_Casemapped/symbols';
import emojiPresentation from '@unicode/unicode-18.0.0/Binary_Property/Emoji_Presentation/symbols';
import whiteSpace from '@unicode/unicode-18.0.0/Binary_Property/White_Space/symbols';
import combiningDiacriticalMarks from '@unicode/unicode-18.0.0/Block/Combining_Diacritical_Marks/symbols';
import halfwidthAndFullwidthForms from '@unicode/unicode-18.0.0/Block/Halfwidth_And_Fullwidth_Forms/symbols';
import decimalNumber from '@unicode/unicode-18.0.0/General_Category/Decimal_Number/symbols';
import format from '@unicode/unicode-18.0.0/General_Category/Format/symbols';
import arabic from '@unicode/unicode-18.0.0/Script/Arabic/symbols';
import cyrillic from '@unicode/unicode-18.0.0/Script/Cyrillic/symbols';
import devanagari from '@unicode/unicode-18.0.0/Script/Devanagari/symbols';
import greek from '@unicode/unicode-18.0.0/Script/Greek/symbols';
import han from '@unicode/unicode-18.0.0/Script/Han/symbols';
import hangul from '@unicode/unicode-18.0.0/Script/Hangul/symbols';
import hebrew from '@unicode/unicode-18.0.0/Script/Hebrew/symbols';
import hiragana from '@unicode/unicode-18.0.0/Script/Hiragana/symbols';
import katakana from '@unicode/unicode-18.0.0/Script/Katakana/symbols';
import latin from '@unicode/unicode-18.0.0/Script/Latin/symbols';
import { alphabetMap } from 'confusables';
import fc from 'fast-check';
const isAscii = (text: string) => !/\P{ASCII}/u.test(text);
const families = {
'latin-accented': latin.filter((c) => /^\p{Ll}$/u.test(c) && !isAscii(c)),
greek,
cyrillic,
hebrew,
arabic,
devanagari,
han,
hiragana,
katakana,
hangul,
'combining-marks': combiningDiacriticalMarks,
fullwidth: halfwidthAndFullwidthForms.filter((c) => /^[\p{L}\p{Nd}]$/u.test(c)),
/** Letters whose case mapping changes the length or does not round-trip, such as ß, fi and İ. */
'case-mapping': changesWhenCasemapped.filter(
(c) =>
c.toUpperCase().length !== c.length ||
c.toLowerCase().length !== c.length ||
c.toLowerCase().toUpperCase() !== c.toUpperCase() ||
c.toUpperCase().toLowerCase() !== c.toLowerCase(),
),
emoji: emojiPresentation,
digits: decimalNumber.filter((c) => !isAscii(c)),
whitespace: whiteSpace,
/** Format characters other than bidi controls: soft hyphen, zero width space and joiners, byte order mark. */
invisible: format.filter((c) => !bidiControl.includes(c)),
'bidi-controls': bidiControl,
/** Non-ASCII characters the `confusables` package maps to ASCII letters. */
confusables: [...'abcdefghijklmnopqrstuvwxyz'].flatMap((letter) =>
(alphabetMap.get(letter) ?? []).filter((c) => !isAscii(c)),
),
} satisfies Record<string, readonly string[]>;
export type FamilyName = keyof typeof families;
export const wordFrom = (family: FamilyName, minLength = 1, maxLength = 8) =>
fc.string({ unit: fc.constantFrom(...families[family]), minLength, maxLength });

View File

@ -0,0 +1,5 @@
// The Unicode data package ships no typings for its per-property modules.
declare module '@unicode/unicode-18.0.0/*/symbols' {
const symbols: string[];
export default symbols;
}

View File

@ -7284,9 +7284,18 @@ importers:
'@types/xml2js':
specifier: 'catalog:'
version: 0.4.14
'@unicode/unicode-18.0.0':
specifier: 2.0.7
version: 2.0.7
'@vitest/coverage-v8':
specifier: 'catalog:'
version: 4.1.9(@vitest/browser@4.1.10)(vitest@4.1.9)
confusables:
specifier: 1.1.1
version: 1.1.1
fast-check:
specifier: 'catalog:'
version: 3.23.2
n8n-containers:
specifier: workspace:*
version: link:../testing/containers
@ -14959,6 +14968,9 @@ packages:
resolution: {integrity: sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og==}
engines: {node: '>=20.0.0'}
'@unicode/unicode-18.0.0@2.0.7':
resolution: {integrity: sha512-sxWcUPwfbWVcSt+RPixHI468bs97A2+ftrC9b6wsGEkUTp2eod0qp7J59A+ngd4mFRISElQhPkYClhtXwNuLQw==}
'@unrs/resolver-binding-android-arm-eabi@1.9.2':
resolution: {integrity: sha512-tS+lqTU3N0kkthU+rYp0spAYq15DU8ld9kXkaKg9sbQqJNF+WPMuNHZQGCgdxrUOEO0j22RKMwRVhF1HTl+X8A==}
cpu: [arm]
@ -16634,6 +16646,10 @@ packages:
config-chain@1.1.13:
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
confusables@1.1.1:
resolution: {integrity: sha512-BzFtzUrufackm00Wb2zvrZV0ItRqPdWaUprU5FXHeZiJRrOWxGmXmQl/muGTF9EQl+MdBXz+Irk99meskGZmXw==}
engines: {node: '>=10', npm: '>=6'}
consola@3.4.2:
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
engines: {node: ^14.18.0 || >=16.10.0}
@ -29026,7 +29042,7 @@ snapshots:
mongodb: 6.21.0(@aws-sdk/credential-providers@3.808.0)(gcp-metadata@5.3.0(supports-color@8.1.1))(socks@2.8.9)
mysql2: 3.23.1(@types/node@20.19.41)
pdf-parse: 2.4.5
pg: 8.21.0
pg: 8.21.0(pg-native@3.8.0)
playwright: 1.62.1
puppeteer: 24.41.0(bufferutil@4.0.9)(supports-color@8.1.1)(typescript@7.0.2)(utf-8-validate@5.0.10)
redis: 4.6.14
@ -33694,6 +33710,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@unicode/unicode-18.0.0@2.0.7': {}
'@unrs/resolver-binding-android-arm-eabi@1.9.2':
optional: true
@ -35618,6 +35636,8 @@ snapshots:
ini: 1.3.8
proto-list: 1.2.4
confusables@1.1.1: {}
consola@3.4.2: {}
console-control-strings@1.1.0:
@ -41480,14 +41500,9 @@ snapshots:
libpq: 1.11.0
pg-types: 2.2.0
pg-pool@3.14.0(pg@8.21.0(pg-native@3.8.0)):
dependencies:
pg: 8.21.0(pg-native@3.8.0)
pg-pool@3.14.0(pg@8.21.0):
dependencies:
pg: 8.21.0
optional: true
pg: 8.21.0(pg-native@3.8.0)
pg-promise@11.9.1(pg-native@3.8.0):
dependencies:
@ -41513,21 +41528,10 @@ snapshots:
postgres-date: 1.0.7
postgres-interval: 1.2.0
pg@8.21.0:
dependencies:
pg-connection-string: 2.14.0
pg-pool: 3.14.0(pg@8.21.0)
pg-protocol: 1.15.0
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.4.0
optional: true
pg@8.21.0(pg-native@3.8.0):
dependencies:
pg-connection-string: 2.14.0
pg-pool: 3.14.0(pg@8.21.0(pg-native@3.8.0))
pg-pool: 3.14.0(pg@8.21.0)
pg-protocol: 1.15.0
pg-types: 2.2.0
pgpass: 1.0.5