PronounsPage/test/buildPronoun.test.ts

523 lines
21 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { beforeEach, describe, expect, test } from 'vitest';
import type { NullPronounsConfig } from '~/locale/config.ts';
import type { Translations } from '~/locale/translations.ts';
import { buildPronoun, buildPronounUsage } from '~/src/buildPronoun.ts';
import { PronounGroup, Pronoun, PronounLibrary } from '~/src/classes.ts';
import { Translator } from '~/src/translator.ts';
import { configWithPronouns } from '~/test/fixtures/config.ts';
import pronounsFactory, { generated as generatedPronouns } from '~/test/fixtures/pronouns.ts';
const translations: Translations = {};
beforeEach(() => {
translations.pronouns = {
any: {
short: 'any',
},
slashes: {
plural: 'plural',
pluralHonorific: 'plural-honorific',
description: 'description',
},
or: 'or',
};
});
const translator = new Translator(translations, translations, configWithPronouns);
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 = buildPronoun(pronouns, 'they', config, translator);
expect(actual).toBeDefined();
expect(actual).toBe(pronouns.they);
});
test('finds pronouns case insensitively by canonical name', () => {
const actual = buildPronoun(pronouns, 'They', config, translator);
expect(actual).toBeDefined();
expect(actual).toBe(pronouns.they);
});
test('finds pronouns by alias', () => {
const actual = buildPronoun(pronouns, 'they/them', config, translator);
expect(actual).toBeDefined();
expect(actual).toBe(pronouns.they);
});
test('finds pronouns case insensitively by alias', () => {
const actual = buildPronoun(pronouns, 'They/Them', config, translator);
expect(actual).toBeDefined();
expect(actual).toBe(pronouns.they);
});
const emojiPronounsConfig = {
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: ['💫', '💙'],
};
describe('when configured that emojiself pronouns are available', () => {
beforeEach(() => {
config.pronouns.emoji = emojiPronounsConfig;
});
test('builds pronouns from emoji', () => {
const actual = buildPronoun(pronouns, '💙', config, translator);
expect(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,
));
});
});
const nullPronounsConfig: NullPronounsConfig = {
routes: ['avoiding', 'no-pronouns', 'null', 'pronounless', 'nullpronominal', 'nameself'],
morphemes: {
pronoun_subject: '#',
pronoun_object: '#',
possessive_determiner: '#\'s',
possessive_pronoun: '#\'s',
reflexive: '#self',
},
examples: [':S'],
};
describe('when configured that null pronouns are available', () => {
beforeEach(() => {
config.pronouns.null = nullPronounsConfig;
translations.pronouns.null = {
description: 'Some people prefer not using any pronouns',
};
});
const expectedDescription = '{/avoiding=avoiding} / {/no-pronouns=no-pronouns} / {/null=null} / ' +
'{/pronounless=pronounless} / {/nullpronominal=nullpronominal} / {/nameself=nameself}';
test('builds pronouns from name', () => {
const actual = buildPronoun(pronouns, ':S', config, translator);
expect(actual).toEqual(new Pronoun(
config,
'S',
expectedDescription,
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,
null,
null,
null,
false,
true,
));
});
test('builds nothing if name too long', () => {
expect(buildPronoun(pronouns, ':Abcdefghijklmnopqrstuvwxyz0123456', 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 = buildPronoun(pronouns, ':S', config, translator);
expect(actual).toEqual(new Pronoun(
config,
'S',
expectedDescription,
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,
null,
null,
null,
false,
true,
));
});
});
});
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 = buildPronoun(pronouns, 'ae/aer/aer/aers/aerself', config, translator);
expect(actual).toBeDefined();
expect(actual).toEqual(generatedPronouns.aer(config));
});
test('unescapes morphemes', () => {
const actual = buildPronoun(pronouns, 's`/he/hir/hir/hirs/hirself', config, translator);
expect(actual).toBeDefined();
expect(actual).toEqual(generatedPronouns.sSlashHe(config));
});
test('builds generated pronoun from all required morphemes and plural modifier', () => {
const actual = buildPronoun(pronouns, 'ae/aer/aer/aers/aerselves/:plural', config, translator);
expect(actual).toBeDefined();
expect(actual).toEqual(generatedPronouns.aerPlural(config));
});
test('builds generated pronoun from all required morphemes and plural honorific modifier', () => {
const actual = buildPronoun(pronouns, 'ae/aer/aer/aers/aerselves/:plural-honorific', config, translator);
expect(actual).toBeDefined();
expect(actual).toEqual(generatedPronouns.aerPluralHonorific(config));
});
test('builds generated pronoun from all required morphemes and description', () => {
const actual = buildPronoun(
pronouns,
'ae/aer/aer/aers/aerself/:description=Neopronoun “ae” `/ “æ”',
config,
translator,
);
expect(actual).toBeDefined();
expect(actual).toEqual(generatedPronouns.aerWithDescription(config));
});
test('builds generated pronoun from all required morphemes and modifiers', () => {
const actual = buildPronoun(
pronouns,
'ae/aer/:description=Neopronoun “ae” `/ “æ”/:plural/aer/aers/aerselves',
config,
translator,
);
expect(actual).toBeDefined();
expect(actual).toEqual(generatedPronouns.aerPluralWithDescription(config));
});
test('builds generated pronoun with some morphemes empty', () => {
const actual = buildPronoun(pronouns, 'ae/aer/aer/ /aerself', config, translator);
expect(actual).toBeDefined();
expect(actual).toEqual(generatedPronouns.aerWithEmptyPossessivePronoun(config));
});
test('builds generated pronoun with morpheme at end empty', () => {
const actual = buildPronoun(pronouns, 'ae/aer/aer/aers/', config, translator);
expect(actual).toBeDefined();
expect(actual).toEqual(generatedPronouns.aerWithEmptyReflexive(config));
});
test('builds generated pronoun with some morphemes unset', () => {
const actual = buildPronoun(pronouns, 'ae/aer/aer/~/aerself', config, translator);
expect(actual).toBeDefined();
expect(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 = buildPronoun(pronouns, 'ae/aer/aer/aerself', config, translator);
expect(actual).toBeDefined();
expect(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 = buildPronoun(pronouns, 'ae,aer,aer,aers,aerself,0,', config, translator);
expect(actual).toBeDefined();
expect(actual).toEqual(generatedPronouns.aer(config));
});
test('succeeds with description missing present', () => {
const actual = buildPronoun(pronouns, 'ae,aer,aer,aers,aerself,0', config, translator);
expect(actual).toBeDefined();
expect(actual).toEqual(generatedPronouns.aer(config));
});
test('succeeds with base pronoun and some custom morphemes', () => {
const actual = buildPronoun(pronouns, 'they,!2,aers,!2,', config, translator);
expect(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 = buildPronoun(pronouns, 'he&she', config, translator);
expect(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 = buildPronoun(pronouns, 'he&she&they', config, translator);
expect(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,
));
});
});
describe('building the short of a pronoun usage', () => {
const pronounGroups = [new PronounGroup('Binary forms', ['he', 'she'], null, 'normative')];
const pronounLibrary = new PronounLibrary(config, pronounGroups, pronouns);
test('of a canonical pronoun', () => {
const actual = buildPronounUsage(pronounLibrary, 'she', config, translator);
expect(actual).toEqual({ short: { options: ['she/her'], glue: ' or ' }, pronoun: pronouns.she });
});
test('of a generated pronoun in slash format', () => {
config.pronouns.honorifics = true;
config.pronouns.generator!.slashes = true;
const actual = buildPronounUsage(pronounLibrary, 'ae/aer/aer/aers/aerself', config, translator);
expect(actual).toEqual({ short: { options: ['ae/aer'], glue: ' or ' }, pronoun: generatedPronouns.aer(config) });
});
test('of interchangeable', () => {
const actual = buildPronounUsage(pronounLibrary, 'he&they', config, translator);
expect(actual).toMatchObject({ short: { options: ['he/him', 'they/them'], glue: ' or ' } });
});
test('of null pronoun', () => {
config.pronouns.null = nullPronounsConfig;
const actual = buildPronounUsage(pronounLibrary, ':A', config, translator);
expect(actual).toMatchObject({ short: { options: ['A/A\'s'], glue: ' or ' } });
});
describe('of null route', () => {
beforeEach(() => {
config.pronouns.null = nullPronounsConfig;
});
test('without specific translation', () => {
const actual = buildPronounUsage(pronounLibrary, 'avoiding', config, translator);
expect(actual).toEqual({ short: { options: ['avoiding'] } });
});
test('with specific translation', () => {
translations.pronouns.null = {
short: {
'no-pronouns': 'no pronouns',
},
};
const actual = buildPronounUsage(pronounLibrary, 'no-pronouns', config, translator);
expect(actual).toEqual({ short: { options: ['no pronouns'] } });
});
test('is case insensitive', () => {
const actual = buildPronounUsage(pronounLibrary, 'Avoiding', config, translator);
expect(actual).toEqual({ short: { options: ['avoiding'] } });
});
});
test('of emojiself', () => {
config.pronouns.emoji = emojiPronounsConfig;
const actual = buildPronounUsage(pronounLibrary, '💫', config, translator);
expect(actual).toMatchObject({ short: { options: ['💫/💫\'s'] } });
});
describe('of mirror pronouns', () => {
beforeEach(() => {
config.pronouns.mirror = {
route: 'mirror',
name: 'Mirror pronouns / Mirrorpronominal',
description: 'A person who uses mirror pronouns wants to be referred to with the same pronouns as the person talking.',
example: [
'Person A uses mirror pronouns.',
'Person B uses {/she=she/her}, so when she talks about person A, she uses “she/her” to refer to her.',
'Person C uses {/ze=ze/hir} interchangeably with {/fae=fæ/fær}, so when ze talks about person A, fæ uses either ze/hir or fæ/fær to refer to fær.',
],
};
});
test('without specific translation', () => {
const actual = buildPronounUsage(pronounLibrary, 'mirror', config, translator);
expect(actual).toEqual({ short: { options: ['mirror'] } });
});
test('with specific translation', () => {
translations.pronouns.mirror = {
short: 'mirror pronouns',
};
const actual = buildPronounUsage(pronounLibrary, 'mirror', config, translator);
expect(actual).toEqual({ short: { options: ['mirror pronouns'] } });
});
test('is case insensitive', () => {
const actual = buildPronounUsage(pronounLibrary, 'Mirror', config, translator);
expect(actual).toEqual({ short: { options: ['mirror'] } });
});
});
describe('of any route', () => {
test('using route name', () => {
const actual = buildPronounUsage(pronounLibrary, 'any', config, translator);
expect(actual).toEqual({ short: { options: ['any'] } });
});
test('is case insensitive', () => {
const actual = buildPronounUsage(pronounLibrary, 'Any', config, translator);
expect(actual).toEqual({ short: { options: ['any'] } });
});
});
describe('of any group', () => {
test('without specific translation', () => {
const actual = buildPronounUsage(pronounLibrary, 'any:normative', config, translator);
expect(actual).toEqual({ short: { options: ['any normative'] } });
});
test('with specific translation', () => {
translations.pronouns.any.group = {
normative: {
short: 'both binaries',
},
};
const actual = buildPronounUsage(pronounLibrary, 'any:normative', config, translator);
expect(actual).toEqual({ short: { options: ['both binaries'] } });
});
test('is case insensitive', () => {
const actual = buildPronounUsage(pronounLibrary, 'Any:Normative', config, translator);
expect(actual).toEqual({ short: { options: ['any normative'] } });
});
test('of unknown yields nothing', () => {
const actual = buildPronounUsage(pronounLibrary, 'any:unknown', config, translator);
expect(actual).toBeNull();
});
});
});