From 93bdc4745e7e68f994b2bc81a7a9851bf4bf3c15 Mon Sep 17 00:00:00 2001 From: Jacob Andersson Date: Tue, 20 Jun 2023 17:25:01 +0200 Subject: [PATCH] fix: support _self target --- playground/app.vue | 20 ++++++- src/index.ts | 22 +++++-- test/module.spec.ts | 139 ++++++++++++++++++++++++++++++-------------- 3 files changed, 132 insertions(+), 49 deletions(-) diff --git a/playground/app.vue b/playground/app.vue index a285411..07e33f4 100644 --- a/playground/app.vue +++ b/playground/app.vue @@ -33,12 +33,14 @@ const valueForm = useState('v3', randomHash) +
Dynamic:
+ Form: @@ -48,13 +50,25 @@ const valueForm = useState('v3', randomHash) :href="$route.path" data-turbo-frame="form-frame dynamic-frame" data-testid="link" - >Visit link + >data-turbo-frame="form-frame dynamic-frame" + data-turbo-frame="dynamic-frame _self"
- + +
+ +
+
@@ -84,5 +98,7 @@ span { grid-column: 1 / 3; white-space: nowrap; margin-top: 10px; + display: grid; + gap: 5px; } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 2eaa079..351eb71 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,13 +28,27 @@ const refreshFrames = (initiator: HTMLElement, response: any, submitter?: HTMLEl const targetFrame = initiator.dataset.turboFrame || '' // Find frames to refresh + let selfFrameTargetIndex = -1 const frames = targetFrame .split(' ') - .filter(Boolean) - .map(id => document.querySelector(`turbo-frame#${id}`)) + .filter((value, index, array) => value && array.indexOf(value) === index) // Remove duplicates + .map((id, index) => { + if (id === '_self') { + // Support data-turbo-frame="_self" + selfFrameTargetIndex = index + return initiator.closest('turbo-frame') + } else { + // Support data-turbo-frame="frame_id" + return document.querySelector(`turbo-frame#${id}`) + } + }) - // Turbo will refresh the first frame natively - frames.shift() + if (targetFrame.includes('_self') && selfFrameTargetIndex >= 0) + // If _self is provided, Turbo will refresh it natively + frames.splice(selfFrameTargetIndex, 1) + else + // Turbo will refresh the first frame natively + frames.shift() // Refresh other frames frames.forEach(frame => { diff --git a/test/module.spec.ts b/test/module.spec.ts index 9aeba82..8b8700d 100644 --- a/test/module.spec.ts +++ b/test/module.spec.ts @@ -9,49 +9,101 @@ describe('Test suite', async () => { }) describe('Native Turbo', async () => { - test('Form submission', testTemplate({ - target: 'form', - updates: { static: false, dynamic: false, form: true }, - })) - - test('Link navigation', testTemplate({ - target: 'link', - updates: { static: false, dynamic: false, form: true }, - })) + describe('Form submission', async () => { + test('#form should only refresh _self', testTemplate({ + target: 'form', + updates: { static: false, dynamic: false, form: true }, + })) + + test('#form-self should only refresh _self', testTemplate({ + target: 'form-self', + updates: { static: false, dynamic: false, form: true }, + })) + }) + + describe('Link navigation', async () => { + test('#link should only refresh _self', testTemplate({ + target: 'link', + updates: { static: false, dynamic: false, form: true }, + })) + + test('#link-self should only refresh _self', testTemplate({ + target: 'link-self', + updates: { static: false, dynamic: false, form: true }, + })) + }) }) - describe('Module enabled', async () => { - test('Form submission', testTemplate({ - target: 'form', - updates: { static: false, dynamic: true, form: true }, - setup: page => page.evaluate('window.$module.enable()'), - })) - - test('Link navigation', testTemplate({ - target: 'link', - updates: { static: false, dynamic: true, form: true }, - setup: page => page.evaluate('window.$module.enable()'), - })) + describe('With module enabled', async () => { + describe('Form submission', async () => { + test('#form should refresh _self + dynamic', testTemplate({ + target: 'form', + updates: { static: false, dynamic: true, form: true }, + setup: page => page.evaluate('window.$module.enable()'), + })) + + test('#form-self should refresh _self + dynamic', testTemplate({ + target: 'form-self', + updates: { static: false, dynamic: true, form: true }, + setup: page => page.evaluate('window.$module.enable()'), + })) + }) + + describe('Link navigation', async () => { + test('#link should refresh _self + dynamic', testTemplate({ + target: 'link', + updates: { static: false, dynamic: true, form: true }, + setup: page => page.evaluate('window.$module.enable()'), + })) + + test('#link-self should refresh _self + dynamic', testTemplate({ + target: 'link-self', + updates: { static: false, dynamic: true, form: true }, + setup: page => page.evaluate('window.$module.enable()'), + })) + }) }) - describe('Module disabled', async () => { - test('Form submission', testTemplate({ - target: 'form', - updates: { static: false, dynamic: false, form: true }, - setup: async (page) => { - await page.evaluate('window.$module.enable()') - await page.evaluate('window.$module.disable()') - }, - })) - - test('Link navigation', testTemplate({ - target: 'link', - updates: { static: false, dynamic: false, form: true }, - setup: async (page) => { - await page.evaluate('window.$module.enable()') - await page.evaluate('window.$module.disable()') - }, - })) + describe('With module enabled then disabled', async () => { + describe('Form submission', async () => { + test('#form should only refresh _self', testTemplate({ + target: 'form', + updates: { static: false, dynamic: false, form: true }, + setup: async (page) => { + await page.evaluate('window.$module.enable()') + await page.evaluate('window.$module.disable()') + }, + })) + + test('#form-self should only refresh _self', testTemplate({ + target: 'form-self', + updates: { static: false, dynamic: false, form: true }, + setup: async (page) => { + await page.evaluate('window.$module.enable()') + await page.evaluate('window.$module.disable()') + }, + })) + }) + + describe('Link navigation', async () => { + test('#link should only refresh _self', testTemplate({ + target: 'link', + updates: { static: false, dynamic: false, form: true }, + setup: async (page) => { + await page.evaluate('window.$module.enable()') + await page.evaluate('window.$module.disable()') + }, + })) + + test('#link-self should only refresh _self', testTemplate({ + target: 'link-self', + updates: { static: false, dynamic: false, form: true }, + setup: async (page) => { + await page.evaluate('window.$module.enable()') + await page.evaluate('window.$module.disable()') + }, + })) + }) }) }) @@ -62,9 +114,10 @@ const getSnapshot = (page: Page) => Promise.all([ page.getByTestId('form-value').textContent(), ]) +type Target = T | `${T}-${string}` const testTemplate = (opt: { setup?: (checkbox: Page) => unknown, - target: 'link' | 'form', + target: Target<'form' | 'link'>, updates: { static: boolean dynamic: boolean @@ -78,10 +131,10 @@ const testTemplate = (opt: { opt.setup?.(page) await new Promise(resolve => setTimeout(resolve, 50)) - if (opt.target === 'link') - await page.getByTestId('link').click() + if (opt.target.startsWith('link')) + await page.getByTestId(opt.target).click() else - await page.getByTestId('form').evaluate((form: HTMLFormElement) => form.requestSubmit()) + await page.getByTestId(opt.target).evaluate((form: HTMLFormElement) => form.requestSubmit()) await turboFormRequest await new Promise(resolve => setTimeout(resolve, 50))