import { beforeEach, describe, expect, jest, test } from '@jest/globals'; import { Translator } from '../src/translator.js'; import type { NullPronounsConfig } from '../locale/config.ts'; const translations = { pronouns: { slashes: { plural: 'plural', pluralHonorific: 'plural-honorific', description: 'description', }, }, }; const translator = new Translator(translations, translations, []); // workaround to be independent of the current selected locale jest.unstable_mockModule('../data/pronouns/morphemes.js', () => { return { default: ['pronoun_subject', 'pronoun_object', 'possessive_determiner', 'possessive_pronoun', 'reflexive'], }; }); const { Pronoun } = await import('../src/classes.ts'); const { buildPronoun } = await import('../src/buildPronoun.ts'); const { default: pronouns, generated: generatedPronouns } = await import('./fixtures/pronouns.ts'); beforeEach(() => { global.config.pronouns = { enabled: true, route: 'pronouns', default: 'he', any: 'any', plurals: true, honorifics: false, multiple: { name: 'Interchangeable forms', description: 'โ€ฆ', examples: ['he&she'], }, null: false, emoji: false, }; }); test('finds pronouns by canonical name', () => { const actual = expect(buildPronoun(pronouns, 'they', translator)); actual.toBeDefined(); actual.toBe(pronouns.they); }); test('finds pronouns by alias', () => { const actual = expect(buildPronoun(pronouns, 'they/them', translator)); actual.toBeDefined(); actual.toBe(pronouns.they); }); describe('when configured that emojiself pronouns are available', () => { beforeEach(() => { global.config.pronouns.emoji = { description: 'Emojiself pronouns', history: 'Emojiself pronouns are intended for online communication and not supposed to be pronounced', morphemes: { pronoun_subject: '#', pronoun_object: '#', possessive_determiner: '#\'s', possessive_pronoun: '#\'s', reflexive: '#self', }, examples: ['๐Ÿ’ซ', '๐Ÿ’™'], template: 'โ€ฆ', }; }); test('builds pronouns from emoji', () => { const actual = expect(buildPronoun(pronouns, '๐Ÿ’™', translator)); actual.toBeDefined(); actual.toEqual(new Pronoun( '๐Ÿ’™', 'Emojiself pronouns', false, { pronoun_subject: '๐Ÿ’™', pronoun_object: '๐Ÿ’™', possessive_determiner: '๐Ÿ’™\'s', possessive_pronoun: '๐Ÿ’™\'s', reflexive: '๐Ÿ’™self', }, [false], [false], [], 'Emojiself pronouns are intended for online communication and not supposed to be pronounced@__generator__', false, )); }); }); describe('when configured that null pronouns are available', () => { const nullPronounsConfig: NullPronounsConfig = { description: 'Avoiding gendered forms', history: 'Some people prefer not using any pronouns', morphemes: { pronoun_subject: '#', pronoun_object: '#', possessive_determiner: '#\'s', possessive_pronoun: '#\'s', reflexive: '#self', }, examples: [':S'], template: 'โ€ฆ', }; beforeEach(() => { global.config.pronouns.null = nullPronounsConfig; }); test('builds pronouns from name', () => { const actual = expect(buildPronoun(pronouns, ':S', translator)); actual.toBeDefined(); actual.toEqual(new Pronoun( 'S', 'Avoiding gendered forms', false, { pronoun_subject: 'S', pronoun_object: 'S', possessive_determiner: 'S\'s', possessive_pronoun: 'S\'s', reflexive: 'Sself', }, [false], [false], [], 'Some people prefer not using any pronouns@__generator__', false, )); }); test('builds nothing if name too long', () => { expect(buildPronoun(pronouns, ':Abcdefghijklmnopqrstuvwxyz', translator)).toBeNull(); }); describe('with conditional placeholders', () => { beforeEach(() => { nullPronounsConfig.morphemes!.possessive_pronoun = '#/[sxzรŸ]$/i#โ€™|#s'; }); test.each(['S', 'Ringelnatz', 'Max', 'X'])('builds morpheme conditionally if name matches', (name) => { expect(buildPronoun(pronouns, `:${name}`, translator)!.morphemes.possessive_pronoun).toBe(`${name}โ€™`); }); test.each(['Sofi', 'Xavier'])('builds morpheme by default if name does not match', (name) => { expect(buildPronoun(pronouns, `:${name}`, translator)!.morphemes.possessive_pronoun).toBe(`${name}s`); }); }); describe('when some morphemes are not defined in template', () => { beforeEach(() => { nullPronounsConfig.morphemes = { pronoun_subject: '#', pronoun_object: '#', possessive_determiner: '#\'s', possessive_pronoun: '#\'s', }; }); test('they become null', () => { const actual = expect(buildPronoun(pronouns, ':S', translator)); actual.toBeDefined(); actual.toEqual(new Pronoun( 'S', 'Avoiding gendered forms', false, { pronoun_subject: 'S', pronoun_object: 'S', possessive_determiner: 'S\'s', possessive_pronoun: 'S\'s', reflexive: null, }, [false], [false], [], 'Some people prefer not using any pronouns@__generator__', false, )); }); }); }); describe('when configured that slashes contain all morphemes', () => { beforeEach(() => { global.config.pronouns.honorifics = true; global.config.pronouns.slashes = true; }); test('builds generated pronoun from all morphemes', () => { const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerself', translator)); actual.toBeDefined(); actual.toEqual(generatedPronouns.aer); }); test('unescapes morphemes', () => { const actual = expect(buildPronoun(pronouns, 's`/he/hir/hir/hirs/hirself', translator)); actual.toBeDefined(); actual.toEqual(generatedPronouns.sSlashHe); }); test('builds generated pronoun from all required morphemes and plural modifier', () => { const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerselves/:plural', translator)); actual.toBeDefined(); actual.toEqual(generatedPronouns.aerPlural); }); test('builds generated pronoun from all required morphemes and plural honorific modifier', () => { const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerselves/:plural-honorific', translator)); actual.toBeDefined(); actual.toEqual(generatedPronouns.aerPluralHonorific); }); test('builds generated pronoun from all required morphemes and description', () => { const actual = expect(buildPronoun( pronouns, 'ae/aer/aer/aers/aerself/:description=Neopronoun โ€œaeโ€ `/ โ€œรฆโ€', translator, )); actual.toBeDefined(); actual.toEqual(generatedPronouns.aerWithDescription); }); test('builds generated pronoun from all required morphemes and modifiers', () => { const actual = expect(buildPronoun( pronouns, 'ae/aer/:description=Neopronoun โ€œaeโ€ `/ โ€œรฆโ€/:plural/aer/aers/aerselves', translator, )); actual.toBeDefined(); actual.toEqual(generatedPronouns.aerPluralWithDescription); }); test('builds generated pronoun with some morphemes empty', () => { const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/ /aerself', translator)); actual.toBeDefined(); actual.toEqual(generatedPronouns.aerWithEmptyPossessivePronoun); }); test('builds generated pronoun with morpheme at end empty', () => { const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/', translator)); actual.toBeDefined(); actual.toEqual(generatedPronouns.aerWithEmptyReflexive); }); test('builds generated pronoun with some morphemes unset', () => { const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/~/aerself', translator)); actual.toBeDefined(); actual.toEqual(generatedPronouns.aerWithUnsetPossessivePronoun); }); test('builds nothing if morphemes are missing', () => { expect(buildPronoun(pronouns, 'ae/aer/aer/aerself', translator)).toBeNull(); }); test('builds nothing if too many morphemes are given', () => { expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerself/aer', translator)).toBeNull(); }); test('builds nothing if description too long', () => { expect(buildPronoun( pronouns, 'ae/aer/aer/aers/aerself/:description=Neopronoun โ€œaeโ€ `/ โ€œรฆโ€ which is my favorite so you should use it too', translator, )).toBeNull(); }); }); describe('when configured that slashes contain some morphemes', () => { beforeEach(() => { global.config.pronouns.slashes = ['pronoun_subject', 'pronoun_object', 'possessive_determiner', 'reflexive']; }); test('builds generated pronoun from all required morphemes', () => { const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aerself', translator)); actual.toBeDefined(); actual.toEqual(generatedPronouns.aerWithUnsetPossessivePronoun); }); test('builds nothing if morphemes are missing', () => { expect(buildPronoun(pronouns, 'ae/aer/aer', translator)).toBeNull(); }); test('builds nothing if too many morphemes are given', () => { expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerself', translator)).toBeNull(); }); }); describe('when configured that slashes cannot contain morphemes', () => { beforeEach(() => { global.config.pronouns.slashes = false; }); const pathBase = 'ae/aer/aer/aers/aerself/aer'; test.each([3, 4, 5, 6])('builds nothing if %d morphemes are given', (count) => { const path = pathBase.split('/').slice(0, count) .join('/'); expect(buildPronoun(pronouns, path.slice(0, count), translator)).toBeUndefined(); }); }); describe('building generated pronouns from commas', () => { test('succeeds with all parts present', () => { const actual = expect(buildPronoun(pronouns, 'ae,aer,aer,aers,aerself,0,', translator)); actual.toBeDefined(); actual.toEqual(generatedPronouns.aer); }); test('succeeds with description missing present', () => { const actual = expect(buildPronoun(pronouns, 'ae,aer,aer,aers,aerself,0', translator)); actual.toBeDefined(); actual.toEqual(generatedPronouns.aer); }); test('succeeds with base pronoun and some custom morphemes', () => { const actual = expect(buildPronoun(pronouns, 'they,!2,aers,!2,', translator)); actual.toBeDefined(); actual.toEqual(new Pronoun( 'they/them', '', false, { pronoun_subject: 'they', pronoun_object: 'them', possessive_determiner: 'their', possessive_pronoun: 'aers', reflexive: 'themselves', }, [true], [false], [], '__generator__', false, )); }); test('fails when too few parts are given', () => { expect(buildPronoun(pronouns, 'ae, translator,aer,aer,aers,aerself', translator)).toBeNull(); }); test('fails when many few parts are given', () => { expect(buildPronoun(pronouns, 'ae, translator,aer,aer,aers,aerself,aersing,0,', translator)).toBeNull(); }); test('fails when base pronoun is unknown', () => { expect(buildPronoun(pronouns, 's/he, translator,!2,aers,!2,', translator)).toBeNull(); }); }); describe('builds interchangable pronouns from ampersand', () => { test('two interchangable pronouns', () => { const actual = expect(buildPronoun(pronouns, 'he&she', translator)); actual.toBeDefined(); actual.toEqual(new Pronoun( 'he&she', ['Normative โ€œhe/himโ€', 'Normative โ€œshe/herโ€'], true, { pronoun_subject: 'he&she', pronoun_object: 'him&her', possessive_determiner: 'his&her', possessive_pronoun: 'his&hers', reflexive: 'himself&herself', }, [false, false], [false, false], [], '', false, )); }); test('three interchangable pronouns', () => { const actual = expect(buildPronoun(pronouns, 'he&she&they', translator)); actual.toBeDefined(); actual.toEqual(new Pronoun( 'he&she&they', ['Normative โ€œhe/himโ€', 'Normative โ€œshe/herโ€', 'Singular โ€œtheyโ€'], true, { pronoun_subject: 'he&she&they', pronoun_object: 'him&her&them', possessive_determiner: 'his&her&their', possessive_pronoun: 'his&hers&theirs', reflexive: 'himself&herself&themselves', }, [false, false, true], [false, false, true], [], '', false, )); }); });