@@ -5,3 +5,111 @@ test('Can see homepage', async ({ page }) => {
55
66 await expect ( page . getByText ( "Symfony UX's E2E App" ) ) . toBeVisible ( ) ;
77} ) ;
8+
9+ // NOTE: Browser test for issue #2623 requires the E2E app to be running:
10+ // 1. Start E2E app: docker compose up -d (from apps/e2e)
11+ // 2. Build packages: php .github/build-packages.php
12+ // 3. Run test: pnpm run test:browser
13+ //
14+ // This test simulates the Turbo DOM transition race condition that caused:
15+ // "Error: Tom Select already initialized on this element"
16+ //
17+ // The scenario:
18+ // 1. Autocomplete element with URL is initialized
19+ // 2. Element is removed from DOM (triggers disconnect)
20+ // 3. Element is re-added with changed URL (triggers urlValueChanged + reconnect)
21+ // 4. Without the fix (this.tomSelect = undefined): double-initialization error
22+ // 5. With the fix: smooth reconnection
23+ //
24+ // The fix in src/controller.ts line 117 prevents this error by clearing
25+ // the stale reference to the destroyed TomSelect instance.
26+ //
27+ // COMMENTED OUT: Requires E2E app infrastructure
28+ /*
29+ test('disconnect/reconnect does not cause double-initialization error (issue #2623)', async ({ page, context }) => {
30+ await page.goto('/autocomplete');
31+
32+ // Setup error logging to catch TomSelect errors
33+ let consoleErrors: string[] = [];
34+ page.on('console', (msg) => {
35+ if (msg.type() === 'error') {
36+ consoleErrors.push(msg.text());
37+ }
38+ });
39+
40+ // Create a test scenario with a temporary autocomplete element
41+ await page.evaluate(() => {
42+ const html = `
43+ <div id="test-container">
44+ <select
45+ id="test-autocomplete"
46+ data-controller="autocomplete"
47+ data-autocomplete-url-value="/autocomplete/api"
48+ >
49+ <option value="">Select...</option>
50+ <option value="1">Item 1</option>
51+ <option value="2">Item 2</option>
52+ </select>
53+ </div>
54+ `;
55+ document.body.insertAdjacentHTML('beforeend', html);
56+ });
57+
58+ // Wait for initial TomSelect initialization
59+ await page.waitForSelector('[id="test-autocomplete"].ts-hidden-accessible', { timeout: 5000 });
60+
61+ // Verify it initialized successfully
62+ const tsWrapper = await page.locator('[id="test-autocomplete"] ~ .ts-wrapper').first();
63+ await expect(tsWrapper).toBeVisible();
64+
65+ // Simulate Turbo's DOM morphing:
66+ // 1. Remove the element (triggers disconnect)
67+ // 2. Re-add it with changed URL (triggers urlValueChanged + reconnect)
68+ await page.evaluate(() => {
69+ const container = document.getElementById('test-container');
70+ const select = document.getElementById('test-autocomplete');
71+
72+ if (!select || !container) return;
73+
74+ // Remove (triggers disconnect)
75+ select.remove();
76+
77+ // Simulate DOM morph delay
78+ setTimeout(() => {
79+ // Re-add with changed URL (triggers urlValueChanged)
80+ const newSelect = document.createElement('select');
81+ newSelect.id = 'test-autocomplete';
82+ newSelect.setAttribute('data-controller', 'autocomplete');
83+ newSelect.setAttribute('data-autocomplete-url-value', '/autocomplete/api-v2');
84+ newSelect.innerHTML = `
85+ <option value="">Select...</option>
86+ <option value="1">Item 1</option>
87+ <option value="2">Item 2</option>
88+ `;
89+ container.appendChild(newSelect);
90+ }, 50);
91+ });
92+
93+ // Wait for reconnection
94+ await page.waitForSelector('[id="test-autocomplete"].ts-hidden-accessible', { timeout: 5000 });
95+
96+ // Verify it reconnected successfully
97+ const tsWrapperAfter = await page.locator('[id="test-autocomplete"] ~ .ts-wrapper').first();
98+ await expect(tsWrapperAfter).toBeVisible();
99+
100+ // Critical check: no "already initialized" errors in console
101+ const alreadyInitializedErrors = consoleErrors.filter((err) =>
102+ err.includes('already initialized') || err.includes('Tom Select')
103+ );
104+
105+ expect(alreadyInitializedErrors).toHaveLength(
106+ 0,
107+ `Issue #2623: "${alreadyInitializedErrors.join('; ')}" - The fix (this.tomSelect = undefined) may be missing`
108+ );
109+
110+ // Cleanup
111+ await page.evaluate(() => {
112+ document.getElementById('test-container')?.remove();
113+ });
114+ });
115+ */
0 commit comments