Skip to content

Commit

Permalink
fix: support _self target
Browse files Browse the repository at this point in the history
  • Loading branch information
miii committed Jun 20, 2023
1 parent 45ccc81 commit 93bdc47
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 49 deletions.
20 changes: 18 additions & 2 deletions playground/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ const valueForm = useState('v3', randomHash)
<span data-testid="static-value" v-text="valueStatic" />
</turbo-frame>
</div>

<div class="container">
<span>Dynamic:</span>
<turbo-frame id="dynamic-frame">
<span data-testid="dynamic-value" v-text="valueDynamic" />
</turbo-frame>
</div>

<turbo-frame id="form-frame" class="container">
<span>Form:</span>
<span data-testid="form-value" v-text="valueForm" />
Expand All @@ -48,13 +50,25 @@ const valueForm = useState('v3', randomHash)
:href="$route.path"
data-turbo-frame="form-frame dynamic-frame"
data-testid="link"
>Visit link</a>
>data-turbo-frame="form-frame dynamic-frame"</a>
<a
:href="$route.path"
data-turbo-frame="dynamic-frame _self"
data-testid="link-self"
>data-turbo-frame="dynamic-frame _self"</a>

<form
data-turbo-frame="form-frame dynamic-frame"
data-testid="form"
>
<button type="submit">Submit form</button>
<button type="submit">data-turbo-frame="form-frame dynamic-frame"</button>
</form>

<form
data-turbo-frame="dynamic-frame _self"
data-testid="form-self"
>
<button type="submit">data-turbo-frame="dynamic-frame _self"</button>
</form>
</div>
</turbo-frame>
Expand Down Expand Up @@ -84,5 +98,7 @@ span {
grid-column: 1 / 3;
white-space: nowrap;
margin-top: 10px;
display: grid;
gap: 5px;
}
</style>
22 changes: 18 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<FrameElement>(`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<FrameElement>('turbo-frame')
} else {
// Support data-turbo-frame="frame_id"
return document.querySelector<FrameElement>(`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 => {
Expand Down
139 changes: 96 additions & 43 deletions test/module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()')
},
}))
})
})
})

Expand All @@ -62,9 +114,10 @@ const getSnapshot = (page: Page) => Promise.all([
page.getByTestId('form-value').textContent(),
])

type Target<T extends string> = T | `${T}-${string}`
const testTemplate = (opt: {
setup?: (checkbox: Page) => unknown,
target: 'link' | 'form',
target: Target<'form' | 'link'>,
updates: {
static: boolean
dynamic: boolean
Expand All @@ -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))
Expand Down

0 comments on commit 93bdc47

Please sign in to comment.