Skip to content

Commit cc68f8e

Browse files
committed
test(internationalized-array): add comprehensive test suite
Add 115 tests covering all _key language identification patterns in preparation for sanity_language migration. Test files added: - schema/array.test.ts (22 tests) - schema/object.test.ts (7 tests) - utils/createAddLanguagePatches.test.ts (14 tests) - utils/checkAllLanguagesArePresent.test.ts (13 tests) - components/AddButtons.test.ts (13 tests) - components/DocumentAddButtons.test.ts (14 tests) - fieldActions/index.test.ts (8 tests) - integration/language-lifecycle.test.ts (7 tests) - integration/validation-flow.test.ts (16 tests) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent cc59598 commit cc68f8e

File tree

11 files changed

+1804
-0
lines changed

11 files changed

+1804
-0
lines changed
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
import {describe, expect, it} from 'vitest'
2+
3+
import type {Language, Value} from '../../types'
4+
5+
import {checkAllLanguagesArePresent} from '../../utils/checkAllLanguagesArePresent'
6+
import {createAddLanguagePatches} from '../../utils/createAddLanguagePatches'
7+
import {createMockSchemaType, createValue, createValues, testLanguages} from '../test-utils'
8+
9+
/**
10+
* Integration tests for the language lifecycle
11+
*
12+
* These tests verify the complete flow of:
13+
* 1. Adding languages (creates items with _key as language id)
14+
* 2. Checking presence (uses _key to determine if language exists)
15+
* 3. Removing languages (preserves other items by _key)
16+
* 4. Reordering languages (maintains _key values)
17+
*
18+
* When migrating to sanity_language, ALL these patterns must be updated together.
19+
*/
20+
21+
describe('language lifecycle integration', () => {
22+
const mockSchemaType = createMockSchemaType('internationalizedArrayString')
23+
24+
describe('adding a language creates item with correct _key', () => {
25+
it('creates new item with _key set to language id', () => {
26+
const existingValue: Value[] = []
27+
28+
// Step 1: Add a language
29+
const patches = createAddLanguagePatches({
30+
addLanguageKeys: ['en'],
31+
schemaType: mockSchemaType,
32+
languages: testLanguages,
33+
filteredLanguages: testLanguages,
34+
value: existingValue,
35+
})
36+
37+
// Verify the new item has _key set to the language id
38+
expect(patches).toHaveLength(1)
39+
expect(patches[0].items[0]._key).toBe('en')
40+
41+
// Simulate applying the patch
42+
const newValue: Value[] = [{_key: 'en', value: undefined}]
43+
44+
// Step 2: Verify presence check finds the language via _key
45+
const isPresent = newValue.some((v) => v._key === 'en')
46+
expect(isPresent).toBe(true)
47+
})
48+
49+
it('full add all missing languages flow', () => {
50+
// Start with just English
51+
const existingValue = createValues(['en'])
52+
53+
// Add all missing languages
54+
const patches = createAddLanguagePatches({
55+
addLanguageKeys: [],
56+
schemaType: mockSchemaType,
57+
languages: testLanguages,
58+
filteredLanguages: testLanguages,
59+
value: existingValue,
60+
})
61+
62+
// Should add fr, de, es (missing languages)
63+
expect(patches).toHaveLength(3)
64+
const addedKeys = patches.map((p) => p.items[0]._key)
65+
expect(addedKeys).toContain('fr')
66+
expect(addedKeys).toContain('de')
67+
expect(addedKeys).toContain('es')
68+
69+
// Simulate the final state
70+
const finalValue = createValues(['en', 'fr', 'de', 'es'])
71+
72+
// All languages should now be present
73+
expect(checkAllLanguagesArePresent(testLanguages, finalValue)).toBe(true)
74+
})
75+
})
76+
77+
describe('removing a language preserves others', () => {
78+
it('other items maintain their _key values', () => {
79+
// Start with all languages
80+
const existingValue = createValues(['en', 'fr', 'de', 'es'])
81+
82+
// Remove 'fr' by filtering it out
83+
const afterRemoval = existingValue.filter((v) => v._key !== 'fr')
84+
85+
// Verify remaining items still have correct _keys
86+
expect(afterRemoval).toHaveLength(3)
87+
expect(afterRemoval.map((v) => v._key)).toEqual(['en', 'de', 'es'])
88+
89+
// Adding 'fr' back should work
90+
const patches = createAddLanguagePatches({
91+
addLanguageKeys: ['fr'],
92+
schemaType: mockSchemaType,
93+
languages: testLanguages,
94+
filteredLanguages: testLanguages,
95+
value: afterRemoval,
96+
})
97+
98+
expect(patches).toHaveLength(1)
99+
expect(patches[0].items[0]._key).toBe('fr')
100+
})
101+
})
102+
103+
describe('reordering maintains _key values', () => {
104+
/**
105+
* This simulates the handleRestoreOrder logic from InternationalizedArray.tsx
106+
*/
107+
it('reorders items to match language order while preserving _keys', () => {
108+
// Value is out of order
109+
const outOfOrderValue: Value[] = [
110+
{_key: 'es', value: 'Hola'},
111+
{_key: 'en', value: 'Hello'},
112+
{_key: 'fr', value: 'Bonjour'},
113+
]
114+
115+
// Reorder to match languages array
116+
const reordered = outOfOrderValue
117+
.reduce((acc, v) => {
118+
const newIndex = testLanguages.findIndex((l) => l.id === v._key)
119+
if (newIndex > -1) {
120+
acc[newIndex] = v
121+
}
122+
return acc
123+
}, [] as Value[])
124+
.filter(Boolean)
125+
126+
// Items should now be in language order
127+
expect(reordered.map((v) => v._key)).toEqual(['en', 'fr', 'es'])
128+
129+
// Values should be preserved
130+
expect(reordered.find((v) => v._key === 'en')?.value).toBe('Hello')
131+
expect(reordered.find((v) => v._key === 'fr')?.value).toBe('Bonjour')
132+
expect(reordered.find((v) => v._key === 'es')?.value).toBe('Hola')
133+
})
134+
135+
it('strips invalid _keys during reorder', () => {
136+
const valueWithInvalid: Value[] = [
137+
{_key: 'en', value: 'Hello'},
138+
{_key: 'invalid', value: 'Bad'}, // Not a valid language
139+
{_key: 'fr', value: 'Bonjour'},
140+
]
141+
142+
const reordered = valueWithInvalid
143+
.reduce((acc, v) => {
144+
const newIndex = testLanguages.findIndex((l) => l.id === v._key)
145+
if (newIndex > -1) {
146+
acc[newIndex] = v
147+
}
148+
return acc
149+
}, [] as Value[])
150+
.filter(Boolean)
151+
152+
// Invalid _key should be stripped
153+
expect(reordered).toHaveLength(2)
154+
expect(reordered.map((v) => v._key)).toEqual(['en', 'fr'])
155+
})
156+
})
157+
158+
describe('language key validation integration', () => {
159+
it('verifies _key is used consistently across operations', () => {
160+
// This test documents the complete _key usage pattern
161+
162+
// 1. Creating new items uses _key for language
163+
const patches = createAddLanguagePatches({
164+
addLanguageKeys: ['en'],
165+
schemaType: mockSchemaType,
166+
languages: testLanguages,
167+
filteredLanguages: testLanguages,
168+
value: [],
169+
})
170+
const newItem = patches[0].items[0]
171+
expect(newItem).toHaveProperty('_key', 'en')
172+
173+
// 2. Presence checking uses _key
174+
const value = createValues(['en', 'fr'])
175+
const hasEnglish = value.some((v) => v._key === 'en')
176+
expect(hasEnglish).toBe(true)
177+
178+
// 3. Filtering uses _key
179+
const availableLanguages = testLanguages.filter(
180+
(lang) => !value.some((v) => v._key === lang.id),
181+
)
182+
expect(availableLanguages.map((l) => l.id)).toEqual(['de', 'es'])
183+
184+
// 4. Duplicate detection uses _key
185+
const seenKeys = new Set<string>()
186+
const duplicates = value.filter((v) => {
187+
if (seenKeys.has(v._key)) return true
188+
seenKeys.add(v._key)
189+
return false
190+
})
191+
expect(duplicates).toHaveLength(0)
192+
193+
// 5. Order comparison uses _key
194+
const languagesInUse = testLanguages.filter((l) => value.some((v) => v._key === l.id))
195+
expect(languagesInUse.map((l) => l.id)).toEqual(['en', 'fr'])
196+
})
197+
})
198+
199+
describe('full workflow simulation', () => {
200+
it('simulates a complete document editing workflow', () => {
201+
const languages: Language[] = [
202+
{id: 'en', title: 'English'},
203+
{id: 'fr', title: 'French'},
204+
{id: 'de', title: 'German'},
205+
]
206+
207+
// 1. Start with empty field
208+
let value: Value[] = []
209+
expect(checkAllLanguagesArePresent(languages, value)).toBe(false)
210+
211+
// 2. User adds English
212+
value = [{_key: 'en', value: 'Hello World'}]
213+
expect(value.some((v) => v._key === 'en')).toBe(true)
214+
expect(checkAllLanguagesArePresent(languages, value)).toBe(false)
215+
216+
// 3. User adds French translation
217+
value = [...value, {_key: 'fr', value: 'Bonjour le monde'}]
218+
expect(value.some((v) => v._key === 'fr')).toBe(true)
219+
expect(checkAllLanguagesArePresent(languages, value)).toBe(false)
220+
221+
// 4. User adds German translation
222+
value = [...value, {_key: 'de', value: 'Hallo Welt'}]
223+
expect(checkAllLanguagesArePresent(languages, value)).toBe(true)
224+
225+
// 5. User removes French translation
226+
value = value.filter((v) => v._key !== 'fr')
227+
expect(checkAllLanguagesArePresent(languages, value)).toBe(false)
228+
expect(value.some((v) => v._key === 'fr')).toBe(false)
229+
230+
// 6. English and German values are preserved
231+
expect(value.find((v) => v._key === 'en')?.value).toBe('Hello World')
232+
expect(value.find((v) => v._key === 'de')?.value).toBe('Hallo Welt')
233+
234+
// 7. User can add French back
235+
const patches = createAddLanguagePatches({
236+
addLanguageKeys: ['fr'],
237+
schemaType: mockSchemaType,
238+
languages,
239+
filteredLanguages: languages,
240+
value,
241+
})
242+
expect(patches[0].items[0]._key).toBe('fr')
243+
})
244+
})
245+
})

0 commit comments

Comments
 (0)