mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-08-03 19:17:07 -04:00
382 lines
14 KiB
TypeScript
382 lines
14 KiB
TypeScript
import { beforeEach, describe, expect, jest, test } from '@jest/globals';
|
||
import { Translator } from '../src/translator.js';
|
||
import type { NullPronounsConfig } from '../locale/config.ts';
|
||
|
||
import { configWithPronouns } from './fixtures/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.ts', () => {
|
||
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: pronounsFactory, generated: generatedPronouns } = await import('./fixtures/pronouns.ts');
|
||
|
||
let config = structuredClone(configWithPronouns);
|
||
const pronouns = Object.fromEntries(Object.entries(pronounsFactory)
|
||
.map(([name, pronounFactory]) => [name, pronounFactory(config)]));
|
||
|
||
beforeEach(() => {
|
||
config = structuredClone(configWithPronouns);
|
||
});
|
||
|
||
test('finds pronouns by canonical name', () => {
|
||
const actual = expect(buildPronoun(pronouns, 'they', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toBe(pronouns.they);
|
||
});
|
||
|
||
test('finds pronouns by alias', () => {
|
||
const actual = expect(buildPronoun(pronouns, 'they/them', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toBe(pronouns.they);
|
||
});
|
||
|
||
describe('when configured that emojiself pronouns are available', () => {
|
||
beforeEach(() => {
|
||
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, '💙', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(new Pronoun(
|
||
config,
|
||
'💙',
|
||
'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(() => {
|
||
config.pronouns.null = nullPronounsConfig;
|
||
});
|
||
|
||
test('builds pronouns from name', () => {
|
||
const actual = expect(buildPronoun(pronouns, ':S', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(new Pronoun(
|
||
config,
|
||
'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', config, 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}`, config, translator)!.morphemes.possessive_pronoun)
|
||
.toBe(`${name}’`);
|
||
});
|
||
test.each(['Sofi', 'Xavier'])('builds morpheme by default if name does not match', (name) => {
|
||
expect(buildPronoun(pronouns, `:${name}`, config, 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', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(new Pronoun(
|
||
config,
|
||
'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(() => {
|
||
config.pronouns.honorifics = true;
|
||
config.pronouns.generator!.slashes = true;
|
||
});
|
||
|
||
test('builds generated pronoun from all morphemes', () => {
|
||
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerself', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(generatedPronouns.aer(config));
|
||
});
|
||
test('unescapes morphemes', () => {
|
||
const actual = expect(buildPronoun(pronouns, 's`/he/hir/hir/hirs/hirself', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(generatedPronouns.sSlashHe(config));
|
||
});
|
||
test('builds generated pronoun from all required morphemes and plural modifier', () => {
|
||
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerselves/:plural', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(generatedPronouns.aerPlural(config));
|
||
});
|
||
test('builds generated pronoun from all required morphemes and plural honorific modifier', () => {
|
||
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerselves/:plural-honorific', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(generatedPronouns.aerPluralHonorific(config));
|
||
});
|
||
test('builds generated pronoun from all required morphemes and description', () => {
|
||
const actual = expect(buildPronoun(
|
||
pronouns,
|
||
'ae/aer/aer/aers/aerself/:description=Neopronoun “ae” `/ “æ”',
|
||
config,
|
||
translator,
|
||
));
|
||
actual.toBeDefined();
|
||
actual.toEqual(generatedPronouns.aerWithDescription(config));
|
||
});
|
||
test('builds generated pronoun from all required morphemes and modifiers', () => {
|
||
const actual = expect(buildPronoun(
|
||
pronouns,
|
||
'ae/aer/:description=Neopronoun “ae” `/ “æ”/:plural/aer/aers/aerselves',
|
||
config,
|
||
translator,
|
||
));
|
||
actual.toBeDefined();
|
||
actual.toEqual(generatedPronouns.aerPluralWithDescription(config));
|
||
});
|
||
test('builds generated pronoun with some morphemes empty', () => {
|
||
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/ /aerself', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(generatedPronouns.aerWithEmptyPossessivePronoun(config));
|
||
});
|
||
test('builds generated pronoun with morpheme at end empty', () => {
|
||
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(generatedPronouns.aerWithEmptyReflexive(config));
|
||
});
|
||
test('builds generated pronoun with some morphemes unset', () => {
|
||
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/~/aerself', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(generatedPronouns.aerWithUnsetPossessivePronoun(config));
|
||
});
|
||
test('builds nothing if morphemes are missing', () => {
|
||
expect(buildPronoun(pronouns, 'ae/aer/aer/aerself', config, translator)).toBeNull();
|
||
});
|
||
test('builds nothing if too many morphemes are given', () => {
|
||
expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerself/aer', config, 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',
|
||
config,
|
||
translator,
|
||
)).toBeNull();
|
||
});
|
||
});
|
||
describe('when configured that slashes contain some morphemes', () => {
|
||
beforeEach(() => {
|
||
config.pronouns.generator!.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', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(generatedPronouns.aerWithUnsetPossessivePronoun(config));
|
||
});
|
||
test('builds nothing if morphemes are missing', () => {
|
||
expect(buildPronoun(pronouns, 'ae/aer/aer', config, translator)).toBeNull();
|
||
});
|
||
test('builds nothing if too many morphemes are given', () => {
|
||
expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerself', config, translator)).toBeNull();
|
||
});
|
||
});
|
||
describe('when configured that slashes cannot contain morphemes', () => {
|
||
beforeEach(() => {
|
||
config.pronouns.generator!.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), config, translator)).toBeNull();
|
||
});
|
||
});
|
||
|
||
describe('building generated pronouns from commas', () => {
|
||
test('succeeds with all parts present', () => {
|
||
const actual = expect(buildPronoun(pronouns, 'ae,aer,aer,aers,aerself,0,', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(generatedPronouns.aer(config));
|
||
});
|
||
test('succeeds with description missing present', () => {
|
||
const actual = expect(buildPronoun(pronouns, 'ae,aer,aer,aers,aerself,0', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(generatedPronouns.aer(config));
|
||
});
|
||
test('succeeds with base pronoun and some custom morphemes', () => {
|
||
const actual = expect(buildPronoun(pronouns, 'they,!2,aers,!2,', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(new Pronoun(
|
||
config,
|
||
'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,aer,aer,aers,aerself', config, translator)).toBeNull();
|
||
});
|
||
test('fails when many few parts are given', () => {
|
||
expect(buildPronoun(pronouns, 'ae,aer,aer,aers,aerself,aersing,0,', config, translator)).toBeNull();
|
||
});
|
||
test('fails when base pronoun is unknown', () => {
|
||
expect(buildPronoun(pronouns, 's/he,!2,aers,!2,', config, translator)).toBeNull();
|
||
});
|
||
});
|
||
|
||
describe('builds interchangeable pronouns from ampersand', () => {
|
||
test('two interchangeable pronouns', () => {
|
||
const actual = expect(buildPronoun(pronouns, 'he&she', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(new Pronoun(
|
||
config,
|
||
'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 interchangeable pronouns', () => {
|
||
const actual = expect(buildPronoun(pronouns, 'he&she&they', config, translator));
|
||
actual.toBeDefined();
|
||
actual.toEqual(new Pronoun(
|
||
config,
|
||
'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,
|
||
));
|
||
});
|
||
});
|