kopia lustrzana https://github.com/wagtail/wagtail
Add the ability to reflect query params in SwapController
This is useful when the controller is used in views that would produce an identical response if the page is hard-reloaded with the same params, e.g. listingspull/11652/head
rodzic
c4f953e90f
commit
4fedf3e2d4
|
@ -718,6 +718,138 @@ describe('SwapController', () => {
|
|||
expect(window.location.search).toEqual('');
|
||||
});
|
||||
|
||||
it('should reflect the query params of the request URL if reflect-value is true', async () => {
|
||||
const expectedRequestUrl = '/path/to-src-value/?foo=bar&abc=&xyz=123';
|
||||
|
||||
expect(window.location.search).toEqual('');
|
||||
expect(handleError).not.toHaveBeenCalled();
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
|
||||
const reflectEventHandler = new Promise((resolve) => {
|
||||
document.addEventListener('w-swap:reflect', resolve);
|
||||
});
|
||||
|
||||
formElement.setAttribute('data-w-swap-src-value', expectedRequestUrl);
|
||||
formElement.setAttribute('data-w-swap-reflect-value', 'true');
|
||||
|
||||
formElement.dispatchEvent(
|
||||
new CustomEvent('custom:event', { bubbles: false }),
|
||||
);
|
||||
|
||||
expect(beginEventHandler).not.toHaveBeenCalled();
|
||||
|
||||
jest.runAllTimers(); // search is debounced
|
||||
|
||||
// should fire a begin event before the request is made
|
||||
expect(beginEventHandler).toHaveBeenCalledTimes(1);
|
||||
expect(beginEventHandler.mock.calls[0][0].detail).toEqual({
|
||||
requestUrl: expectedRequestUrl,
|
||||
});
|
||||
|
||||
// visual loading state should be active
|
||||
await Promise.resolve(); // trigger next rendering
|
||||
|
||||
expect(handleError).not.toHaveBeenCalled();
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
expectedRequestUrl,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
const reflectEvent = await reflectEventHandler;
|
||||
|
||||
// should dispatch reflect event
|
||||
expect(reflectEvent.detail).toEqual({
|
||||
requestUrl: expectedRequestUrl,
|
||||
});
|
||||
|
||||
const successEvent = await onSuccess;
|
||||
|
||||
// should dispatch success event
|
||||
expect(successEvent.detail).toEqual({
|
||||
requestUrl: expectedRequestUrl,
|
||||
results: expect.any(String),
|
||||
});
|
||||
|
||||
// should update HTML
|
||||
expect(
|
||||
document.getElementById('content').querySelectorAll('li'),
|
||||
).toHaveLength(2);
|
||||
|
||||
await flushPromises();
|
||||
|
||||
// should update the current URL to have the query params from requestUrl
|
||||
// (except for those that are empty)
|
||||
// as the reflect-value attribute is set to true
|
||||
expect(window.location.search).toEqual('?foo=bar&xyz=123');
|
||||
});
|
||||
|
||||
it('should allow for blocking the reflection of query params with event handlers', async () => {
|
||||
const expectedRequestUrl = '/path/to-src-value/?foo=bar&abc=&xyz=123';
|
||||
|
||||
expect(window.location.search).toEqual('');
|
||||
expect(handleError).not.toHaveBeenCalled();
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
|
||||
const reflectEventHandler = jest.fn((event) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
document.addEventListener('w-swap:reflect', reflectEventHandler);
|
||||
|
||||
formElement.setAttribute('data-w-swap-src-value', expectedRequestUrl);
|
||||
formElement.setAttribute('data-w-swap-reflect-value', 'true');
|
||||
|
||||
formElement.dispatchEvent(
|
||||
new CustomEvent('custom:event', { bubbles: false }),
|
||||
);
|
||||
|
||||
expect(beginEventHandler).not.toHaveBeenCalled();
|
||||
|
||||
jest.runAllTimers(); // search is debounced
|
||||
|
||||
// should fire a begin event before the request is made
|
||||
expect(beginEventHandler).toHaveBeenCalledTimes(1);
|
||||
expect(beginEventHandler.mock.calls[0][0].detail).toEqual({
|
||||
requestUrl: expectedRequestUrl,
|
||||
});
|
||||
|
||||
// visual loading state should be active
|
||||
await Promise.resolve(); // trigger next rendering
|
||||
|
||||
expect(handleError).not.toHaveBeenCalled();
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
expectedRequestUrl,
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
const successEvent = await onSuccess;
|
||||
|
||||
// should dispatch reflect event
|
||||
expect(reflectEventHandler).toHaveBeenCalledTimes(1);
|
||||
expect(reflectEventHandler.mock.calls[0][0].detail).toEqual({
|
||||
requestUrl: expectedRequestUrl,
|
||||
});
|
||||
|
||||
// should dispatch success event
|
||||
expect(successEvent.detail).toEqual({
|
||||
requestUrl: expectedRequestUrl,
|
||||
results: expect.any(String),
|
||||
});
|
||||
|
||||
// should update HTML
|
||||
expect(
|
||||
document.getElementById('content').querySelectorAll('li'),
|
||||
).toHaveLength(2);
|
||||
|
||||
await flushPromises();
|
||||
|
||||
// should NOT update the current URL
|
||||
// as the reflect-value attribute is set to false
|
||||
expect(window.location.search).toEqual('');
|
||||
|
||||
document.removeEventListener('w-swap:reflect', reflectEventHandler);
|
||||
});
|
||||
|
||||
it('should support replace with a url value provided via the Custom event detail', async () => {
|
||||
const expectedRequestUrl = '/path/to/url-in-event-detail/?q=alpha';
|
||||
|
||||
|
@ -767,6 +899,7 @@ describe('SwapController', () => {
|
|||
await flushPromises();
|
||||
|
||||
// should NOT update the current URL
|
||||
// as the reflect-value attribute is not set
|
||||
expect(window.location.search).toEqual('');
|
||||
});
|
||||
|
||||
|
@ -914,9 +1047,174 @@ describe('SwapController', () => {
|
|||
await flushPromises();
|
||||
|
||||
// should NOT update the current URL
|
||||
// as the reflect-value attribute is not set
|
||||
expect(window.location.search).toEqual('');
|
||||
});
|
||||
|
||||
it('should reflect the query params of the request URL if reflect-value is true', async () => {
|
||||
const formElement = document.querySelector('form');
|
||||
formElement.setAttribute('data-w-swap-reflect-value', 'true');
|
||||
|
||||
const input = document.getElementById('search');
|
||||
|
||||
const results = getMockResults({ total: 5 });
|
||||
|
||||
const onSuccess = new Promise((resolve) => {
|
||||
document.addEventListener('w-swap:success', resolve);
|
||||
});
|
||||
|
||||
const beginEventHandler = jest.fn();
|
||||
document.addEventListener('w-swap:begin', beginEventHandler);
|
||||
|
||||
fetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: () => Promise.resolve(results),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(window.location.search).toEqual('');
|
||||
expect(handleError).not.toHaveBeenCalled();
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
|
||||
input.value = 'alpha';
|
||||
document.querySelector('[name="other"]').value = 'something on other';
|
||||
document.querySelector('[name="type"]').value = '';
|
||||
input.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||
|
||||
expect(beginEventHandler).not.toHaveBeenCalled();
|
||||
|
||||
jest.runAllTimers(); // search is debounced
|
||||
|
||||
// should fire a begin event before the request is made
|
||||
expect(beginEventHandler).toHaveBeenCalledTimes(1);
|
||||
expect(beginEventHandler.mock.calls[0][0].detail).toEqual({
|
||||
requestUrl:
|
||||
'/path/to/form/action/?q=alpha&type=&other=something+on+other',
|
||||
});
|
||||
|
||||
// visual loading state should be active
|
||||
await Promise.resolve(); // trigger next rendering
|
||||
|
||||
expect(handleError).not.toHaveBeenCalled();
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'/path/to/form/action/?q=alpha&type=&other=something+on+other',
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
const successEvent = await onSuccess;
|
||||
|
||||
// should dispatch success event
|
||||
expect(successEvent.detail).toEqual({
|
||||
requestUrl:
|
||||
'/path/to/form/action/?q=alpha&type=&other=something+on+other',
|
||||
results: expect.any(String),
|
||||
});
|
||||
|
||||
// should update HTML
|
||||
expect(
|
||||
document.getElementById('task-results').querySelectorAll('li').length,
|
||||
).toBeTruthy();
|
||||
|
||||
await flushPromises();
|
||||
|
||||
// should update the current URL to have the query params from requestUrl
|
||||
// (except for those that are empty)
|
||||
// as the reflect-value attribute is set to true
|
||||
expect(window.location.search).toEqual(
|
||||
'?q=alpha&other=something+on+other',
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow for blocking the reflection of query params with event handlers', async () => {
|
||||
const formElement = document.querySelector('form');
|
||||
formElement.setAttribute('data-w-swap-reflect-value', 'true');
|
||||
|
||||
const reflectEventHandler = jest.fn((event) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
document.addEventListener('w-swap:reflect', reflectEventHandler);
|
||||
|
||||
const input = document.getElementById('search');
|
||||
|
||||
const results = getMockResults({ total: 5 });
|
||||
|
||||
const onSuccess = new Promise((resolve) => {
|
||||
document.addEventListener('w-swap:success', resolve);
|
||||
});
|
||||
|
||||
const beginEventHandler = jest.fn();
|
||||
document.addEventListener('w-swap:begin', beginEventHandler);
|
||||
|
||||
fetch.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: () => Promise.resolve(results),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(window.location.search).toEqual('');
|
||||
expect(handleError).not.toHaveBeenCalled();
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
|
||||
input.value = 'alpha';
|
||||
document.querySelector('[name="other"]').value = 'something on other';
|
||||
document.querySelector('[name="type"]').value = '';
|
||||
input.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||
|
||||
expect(beginEventHandler).not.toHaveBeenCalled();
|
||||
|
||||
jest.runAllTimers(); // search is debounced
|
||||
|
||||
// should fire a begin event before the request is made
|
||||
expect(beginEventHandler).toHaveBeenCalledTimes(1);
|
||||
expect(beginEventHandler.mock.calls[0][0].detail).toEqual({
|
||||
requestUrl:
|
||||
'/path/to/form/action/?q=alpha&type=&other=something+on+other',
|
||||
});
|
||||
|
||||
// visual loading state should be active
|
||||
await Promise.resolve(); // trigger next rendering
|
||||
|
||||
expect(handleError).not.toHaveBeenCalled();
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'/path/to/form/action/?q=alpha&type=&other=something+on+other',
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
const successEvent = await onSuccess;
|
||||
|
||||
// should dispatch reflect event
|
||||
expect(reflectEventHandler).toHaveBeenCalledTimes(1);
|
||||
expect(reflectEventHandler.mock.calls[0][0].detail).toEqual({
|
||||
requestUrl:
|
||||
'/path/to/form/action/?q=alpha&type=&other=something+on+other',
|
||||
});
|
||||
|
||||
// should dispatch success event
|
||||
expect(successEvent.detail).toEqual({
|
||||
requestUrl:
|
||||
'/path/to/form/action/?q=alpha&type=&other=something+on+other',
|
||||
results: expect.any(String),
|
||||
});
|
||||
|
||||
// should update HTML
|
||||
expect(
|
||||
document.getElementById('task-results').querySelectorAll('li').length,
|
||||
).toBeTruthy();
|
||||
|
||||
await flushPromises();
|
||||
|
||||
// should NOT update the current URL
|
||||
// as the reflect-value attribute is set to false
|
||||
expect(window.location.search).toEqual('');
|
||||
|
||||
document.removeEventListener('w-swap:reflect', reflectEventHandler);
|
||||
});
|
||||
|
||||
it('should allow for blocking the request with custom events', async () => {
|
||||
const input = document.getElementById('search');
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ export class SwapController extends Controller<
|
|||
icon: { default: '', type: String },
|
||||
loading: { default: false, type: Boolean },
|
||||
src: { default: '', type: String },
|
||||
reflect: { default: false, type: Boolean },
|
||||
target: { default: '#listing-results', type: String },
|
||||
wait: { default: 200, type: Number },
|
||||
};
|
||||
|
@ -58,6 +59,7 @@ export class SwapController extends Controller<
|
|||
declare iconValue: string;
|
||||
declare loadingValue: boolean;
|
||||
declare srcValue: string;
|
||||
declare reflectValue: boolean;
|
||||
declare targetValue: string;
|
||||
declare waitValue: number;
|
||||
|
||||
|
@ -215,6 +217,20 @@ export class SwapController extends Controller<
|
|||
this.replace(url + queryString);
|
||||
}
|
||||
|
||||
reflectParams(url: string) {
|
||||
const params = new URL(url, window.location.href).searchParams;
|
||||
const filteredParams = new URLSearchParams();
|
||||
params.forEach((value, key) => {
|
||||
// Check if the value is not empty after trimming white space
|
||||
// and if the key is not a Wagtail internal param
|
||||
if (value.trim() !== '' && !key.startsWith('_w_')) {
|
||||
filteredParams.append(key, value);
|
||||
}
|
||||
});
|
||||
const queryString = `?${filteredParams.toString()}`;
|
||||
window.history.replaceState(null, '', queryString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort any existing requests & set up new abort controller, then fetch and replace
|
||||
* the HTML target with the new results.
|
||||
|
@ -259,11 +275,24 @@ export class SwapController extends Controller<
|
|||
})
|
||||
.then((results) => {
|
||||
target.innerHTML = results;
|
||||
|
||||
if (this.reflectValue) {
|
||||
const event = this.dispatch('reflect', {
|
||||
cancelable: true,
|
||||
detail: { requestUrl },
|
||||
target,
|
||||
});
|
||||
if (!event.defaultPrevented) {
|
||||
this.reflectParams(requestUrl);
|
||||
}
|
||||
}
|
||||
|
||||
this.dispatch('success', {
|
||||
cancelable: false,
|
||||
detail: { requestUrl, results },
|
||||
target,
|
||||
});
|
||||
|
||||
return results;
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
Ładowanie…
Reference in New Issue