diff --git a/.changeset/bitter-pigs-type.md b/.changeset/bitter-pigs-type.md new file mode 100644 index 00000000..a845151c --- /dev/null +++ b/.changeset/bitter-pigs-type.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/ui/src/components/bible-chapter-picker.stories.tsx b/packages/ui/src/components/bible-chapter-picker.stories.tsx index 21f0a393..d515ef6a 100644 --- a/packages/ui/src/components/bible-chapter-picker.stories.tsx +++ b/packages/ui/src/components/bible-chapter-picker.stories.tsx @@ -98,6 +98,8 @@ export const LightBackground: Story = { // Clear search and select Genesis const currentSearchInput = overlay.getByPlaceholderText(/search/i); + // user-event cannot clear an input in a shadow root: it checks + // document.activeElement, which is the shadow host. await fireEvent.input(currentSearchInput, { target: { value: '' } }); await waitFor(() => expect(overlay.getByText('Genesis')).toBeInTheDocument()); await userEvent.click(overlay.getByText('Genesis')); diff --git a/packages/ui/src/components/bible-chapter-picker.tsx b/packages/ui/src/components/bible-chapter-picker.tsx index 2ee69ef6..9faf45a4 100644 --- a/packages/ui/src/components/bible-chapter-picker.tsx +++ b/packages/ui/src/components/bible-chapter-picker.tsx @@ -445,6 +445,8 @@ function Content({ onRequestClose, onSelect }: BibleChapterPickerContentProps) { dir="auto" className="yv:text-base yv:leading-normal" value={searchQuery} + // onInput, not onChange: onChange does not update the query inside the picker's + // shadow root. The picker search stories fail with onChange. onInput={(event) => setSearchQuery(event.currentTarget.value)} /> diff --git a/packages/ui/src/components/bible-version-picker.stories.tsx b/packages/ui/src/components/bible-version-picker.stories.tsx index 5ea3f88b..e05bc96c 100644 --- a/packages/ui/src/components/bible-version-picker.stories.tsx +++ b/packages/ui/src/components/bible-version-picker.stories.tsx @@ -377,6 +377,8 @@ export const LanguageSearch: Story = { const currentLanguageSearchInput = overlay.getByRole('textbox', { name: /search languages/i, }); + // user-event cannot clear an input in a shadow root: it checks + // document.activeElement, which is the shadow host. await fireEvent.input(currentLanguageSearchInput, { target: { value: '' } }); await userEvent.type(currentLanguageSearchInput, 'Koreanea', { delay: 50 }); diff --git a/packages/ui/src/components/bible-version-picker.tsx b/packages/ui/src/components/bible-version-picker.tsx index 42651b1c..e13c73f6 100644 --- a/packages/ui/src/components/bible-version-picker.tsx +++ b/packages/ui/src/components/bible-version-picker.tsx @@ -892,6 +892,8 @@ function Content({ open, onRequestClose }: BibleVersionPickerContentProps = {}) placeholder={t('searchPlaceholder')} dir="auto" value={searchQuery} + // onInput, not onChange: onChange does not update the query inside the picker's + // shadow root. The picker search stories fail with onChange. onInput={(event) => setSearchQuery(event.currentTarget.value)} > @@ -1098,6 +1100,8 @@ export function BibleLanguagePickerContent({ placeholder={t('searchPlaceholder')} dir="auto" value={languageSearchQuery} + // onInput, not onChange: onChange does not update the query inside the picker's + // shadow root. The picker search stories fail with onChange. onInput={(event) => setLanguageSearchQuery(event.currentTarget.value)} /> diff --git a/packages/ui/src/lib/shadow-root-host.test.tsx b/packages/ui/src/lib/shadow-root-host.test.tsx index 3d709378..20f586f7 100644 --- a/packages/ui/src/lib/shadow-root-host.test.tsx +++ b/packages/ui/src/lib/shadow-root-host.test.tsx @@ -82,6 +82,7 @@ async function expectStyleSheetFailureRecovery(stage: StyleSheetFailureStage): P ownerDocument.createElement('div').attachShadow({ mode: 'open' }), ); const adoptedStyleSheets = new WeakMap(); + const populatedStyleSheets = new WeakSet(); const failedContainer = ownerDocument.createElement('div'); const recoveredContainer = ownerDocument.createElement('div'); const sharedContainer = ownerDocument.createElement('div'); @@ -116,6 +117,7 @@ async function expectStyleSheetFailureRecovery(stage: StyleSheetFailureStage): P if (stage === 'replacement' && rejectFailure) { throw new Error('replacement failed'); } + populatedStyleSheets.add(this); } }, }); @@ -157,6 +159,8 @@ async function expectStyleSheetFailureRecovery(stage: StyleSheetFailureStage): P return root; }); expect(recoveredRoot.adoptedStyleSheets).toHaveLength(1); + // A sheet cached before a failed replaceSync would be adopted here without SDK styles. + expect(populatedStyleSheets.has(recoveredRoot.adoptedStyleSheets[0]!)).toBe(true); expect(recoveredRoot.querySelector('style')).toBeNull(); const sharedView = render(