feat: added support for adding plurality to ingredients
This commit is contained in:
parent
750bfb912f
commit
8855d6ba19
9 changed files with 468 additions and 8 deletions
|
|
@ -518,4 +518,98 @@ describe('density with approximate values', () => {
|
|||
expect(weightAttrs.length).toBe(1);
|
||||
expect(weightAttrs[0].weightDefault).toMatch(/^~/);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Count Noun Pluralization ───────────────────────────────
|
||||
|
||||
describe('count noun pluralization', () => {
|
||||
it('generates noun span for known singular noun', () => {
|
||||
const html = render('- onion 1');
|
||||
expect(html).toContain('class="count-noun"');
|
||||
expect(html).toContain('data-singular="onion"');
|
||||
expect(html).toContain('data-plural="onions"');
|
||||
});
|
||||
|
||||
it('generates noun span for known plural noun', () => {
|
||||
const html = render('- eggs 3');
|
||||
expect(html).toContain('class="count-noun"');
|
||||
expect(html).toContain('data-singular="egg"');
|
||||
expect(html).toContain('data-plural="eggs"');
|
||||
});
|
||||
|
||||
it('displays singular form when count is 1', () => {
|
||||
const html = render('- onion 1');
|
||||
expect(html).toMatch(/<span class="count-noun"[^>]*>onion<\/span>/);
|
||||
});
|
||||
|
||||
it('displays plural form when count is not 1', () => {
|
||||
const html = render('- egg 3');
|
||||
expect(html).toMatch(/<span class="count-noun"[^>]*>eggs<\/span>/);
|
||||
});
|
||||
|
||||
it('displays plural form when count is 0', () => {
|
||||
const html = render('- egg 0');
|
||||
expect(html).toMatch(/<span class="count-noun"[^>]*>eggs<\/span>/);
|
||||
});
|
||||
|
||||
it('uses max value for ranges (plural)', () => {
|
||||
const html = render('- onion 1-2');
|
||||
// max value is 2 -> plural
|
||||
expect(html).toMatch(/<span class="count-noun"[^>]*>onions<\/span>/);
|
||||
});
|
||||
|
||||
it('adds data-has-noun on the count measurement span', () => {
|
||||
const html = render('- onion 1');
|
||||
expect(html).toContain('data-has-noun="true"');
|
||||
});
|
||||
|
||||
it('does not add data-has-noun for unknown nouns', () => {
|
||||
const html = render('- toaster 1');
|
||||
expect(html).not.toContain('data-has-noun');
|
||||
expect(html).not.toContain('count-noun');
|
||||
});
|
||||
|
||||
it('handles multi-word nouns like bell pepper', () => {
|
||||
const html = render('- bell pepper 2');
|
||||
expect(html).toContain('data-singular="bell pepper"');
|
||||
expect(html).toContain('data-plural="bell peppers"');
|
||||
expect(html).toMatch(/<span class="count-noun"[^>]*>bell peppers<\/span>/);
|
||||
});
|
||||
|
||||
it('handles multi-word nouns like egg yolk', () => {
|
||||
const html = render('- egg yolk 1');
|
||||
expect(html).toContain('data-singular="egg yolk"');
|
||||
expect(html).toMatch(/<span class="count-noun"[^>]*>egg yolk<\/span>/);
|
||||
});
|
||||
|
||||
it('corrects plural to singular for count 1', () => {
|
||||
const html = render('- eggs 1');
|
||||
expect(html).toMatch(/<span class="count-noun"[^>]*>egg<\/span>/);
|
||||
});
|
||||
|
||||
it('corrects singular to plural for count > 1', () => {
|
||||
const html = render('- onion 4');
|
||||
expect(html).toMatch(/<span class="count-noun"[^>]*>onions<\/span>/);
|
||||
});
|
||||
|
||||
it('works in tools section', () => {
|
||||
const md = [
|
||||
'## Ingredients',
|
||||
'- egg 2',
|
||||
].join('\n');
|
||||
const html = render(md);
|
||||
expect(html).toContain('class="count-noun"');
|
||||
expect(html).toMatch(/<span class="count-noun"[^>]*>eggs<\/span>/);
|
||||
});
|
||||
|
||||
it('preserves text before the noun', () => {
|
||||
const html = render('- white onion 1');
|
||||
// "white " should appear before the noun span
|
||||
expect(html).toMatch(/white <span class="count-noun"/);
|
||||
});
|
||||
|
||||
it('does not generate noun span for non-count measurements', () => {
|
||||
const html = render('- onion 200g');
|
||||
expect(html).not.toContain('count-noun');
|
||||
});
|
||||
});
|
||||
106
lib/measurements/__tests__/plurals.test.js
Normal file
106
lib/measurements/__tests__/plurals.test.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
const { matchPlural, PLURALS } = require('../plurals');
|
||||
|
||||
describe('matchPlural', () => {
|
||||
it('returns null for empty/null input', () => {
|
||||
expect(matchPlural('')).toBeNull();
|
||||
expect(matchPlural(null)).toBeNull();
|
||||
expect(matchPlural(undefined)).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for unknown nouns', () => {
|
||||
expect(matchPlural('dragon fruit ')).toBeNull();
|
||||
expect(matchPlural('toaster ')).toBeNull();
|
||||
});
|
||||
|
||||
it('matches singular noun at end of text', () => {
|
||||
const result = matchPlural('onion ');
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.singular).toBe('onion');
|
||||
expect(result.plural).toBe('onions');
|
||||
});
|
||||
|
||||
it('matches plural noun at end of text', () => {
|
||||
const result = matchPlural('eggs ');
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.singular).toBe('egg');
|
||||
expect(result.plural).toBe('eggs');
|
||||
});
|
||||
|
||||
it('is case-insensitive', () => {
|
||||
expect(matchPlural('Onion ')).not.toBeNull();
|
||||
expect(matchPlural('EGGS ')).not.toBeNull();
|
||||
expect(matchPlural('Tomatoes ')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('trims trailing whitespace', () => {
|
||||
const result = matchPlural('onion ');
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.singular).toBe('onion');
|
||||
});
|
||||
|
||||
it('respects word boundaries', () => {
|
||||
// "megg" should not match "egg"
|
||||
expect(matchPlural('megg ')).toBeNull();
|
||||
// "button" should not match as containing a known noun
|
||||
expect(matchPlural('button ')).toBeNull();
|
||||
});
|
||||
|
||||
it('matches multi-word nouns (longest first)', () => {
|
||||
const result = matchPlural('garlic clove ');
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.singular).toBe('garlic clove');
|
||||
expect(result.plural).toBe('garlic cloves');
|
||||
});
|
||||
|
||||
it('matches "clove" when not preceded by "garlic"', () => {
|
||||
const result = matchPlural('clove ');
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.singular).toBe('clove');
|
||||
expect(result.plural).toBe('cloves');
|
||||
});
|
||||
|
||||
it('matches "bell pepper" before "pepper"', () => {
|
||||
const result = matchPlural('bell pepper ');
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.singular).toBe('bell pepper');
|
||||
expect(result.plural).toBe('bell peppers');
|
||||
});
|
||||
|
||||
it('matches "pepper" standalone', () => {
|
||||
const result = matchPlural('pepper ');
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.singular).toBe('pepper');
|
||||
expect(result.plural).toBe('peppers');
|
||||
});
|
||||
|
||||
it('provides correct matchStart and matchLength', () => {
|
||||
const result = matchPlural('white onion ');
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.matchStart).toBe(6); // "white " is 6 chars
|
||||
expect(result.matchLength).toBe(5); // "onion" is 5 chars
|
||||
});
|
||||
|
||||
it('handles text with prefix before noun', () => {
|
||||
const result = matchPlural('- [ ] eggs ');
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.singular).toBe('egg');
|
||||
expect(result.plural).toBe('eggs');
|
||||
});
|
||||
|
||||
it('matches egg yolk and egg white', () => {
|
||||
expect(matchPlural('egg yolk ')).not.toBeNull();
|
||||
expect(matchPlural('egg yolk ').singular).toBe('egg yolk');
|
||||
expect(matchPlural('egg white ')).not.toBeNull();
|
||||
expect(matchPlural('egg white ').singular).toBe('egg white');
|
||||
});
|
||||
|
||||
it('dictionary is sorted longest-first', () => {
|
||||
for (let i = 0; i < PLURALS.length - 1; i++) {
|
||||
const currentLen = Math.max(PLURALS[i].singular.length, PLURALS[i].plural.length);
|
||||
const nextLen = Math.max(PLURALS[i + 1].singular.length, PLURALS[i + 1].plural.length);
|
||||
expect(currentLen).toBeGreaterThanOrEqual(nextLen);
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue