Skip to content

Commit e5cc4e0

Browse files
committed
Starting new child tests
1 parent 6b7fcd2 commit e5cc4e0

File tree

9 files changed

+125
-20
lines changed

9 files changed

+125
-20
lines changed

src/LiveComponent/assets/src/Backend.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@ export default class implements BackendInterface {
2929
'Accept': 'application/vnd.live-component+html',
3030
};
3131

32+
const hasFingerprints = Object.keys(childrenFingerprints).length > 0;
33+
const hasUpdatedModels = Object.keys(updatedModels).length > 0;
3234
if (actions.length === 0 && this.willDataFitInUrl(JSON.stringify(data), params, JSON.stringify(childrenFingerprints))) {
3335
params.set('data', JSON.stringify(data));
34-
params.set('childrenFingerprints', JSON.stringify(childrenFingerprints));
36+
if (hasFingerprints) {
37+
params.set('childrenFingerprints', JSON.stringify(childrenFingerprints));
38+
}
3539
updatedModels.forEach((model) => {
3640
params.append('updatedModels[]', model);
3741
});
@@ -40,10 +44,10 @@ export default class implements BackendInterface {
4044
fetchOptions.method = 'POST';
4145
fetchOptions.headers['Content-Type'] = 'application/json';
4246
const requestData: any = { data };
43-
if (updatedModels) {
47+
if (hasUpdatedModels) {
4448
requestData.updatedModels = updatedModels;
4549
}
46-
if (childrenFingerprints) {
50+
if (hasFingerprints) {
4751
requestData.childrenFingerprints = childrenFingerprints;
4852
}
4953

src/LiveComponent/assets/test/controller/action.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ describe('LiveController Action Tests', () => {
4646

4747
it('immediately sends an action, includes debouncing model updates and cancels those debounce renders', async () => {
4848
const test = await createTest({ comment: '', isSaved: false }, (data: any) => `
49-
<div ${initComponent(data, { debounce: 10 })}>
49+
<div ${initComponent(data, {}, { debounce: 10 })}>
5050
<input data-model="comment" value="${data.comment}">
5151
5252
${data.isSaved ? 'Comment Saved!' : ''}
@@ -134,7 +134,7 @@ describe('LiveController Action Tests', () => {
134134

135135
it('makes model updates wait until action Ajax call finishes', async () => {
136136
const test = await createTest({ comment: 'donut', isSaved: false }, (data: any) => `
137-
<div ${initComponent(data, { debounce: 50 })}>
137+
<div ${initComponent(data, {}, { debounce: 50 })}>
138138
<input data-model="comment" value="${data.comment}">
139139
140140
${data.isSaved ? 'Comment Saved!' : ''}

src/LiveComponent/assets/test/controller/basic.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('LiveController Basic Tests', () => {
3434

3535
it('creates the Component object', async () => {
3636
const test = await createTest({ firstName: 'Ryan' }, (data: any) => `
37-
<div ${initComponent(data, { debounce: 115, id: 'the-id', fingerprint: 'the-fingerprint' })}></div>
37+
<div ${initComponent(data, {}, { debounce: 115, id: 'the-id', fingerprint: 'the-fingerprint' })}></div>
3838
`);
3939

4040
expect(test.controller.component).toBeInstanceOf(Component);

src/LiveComponent/assets/test/controller/child.test.ts

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe('LiveController parent -> child component tests', () => {
2020

2121
it('adds & removes the child correctly', async () => {
2222
const childTemplate = (data: any) => `
23-
<div ${initComponent(data, {id: 'the-child-id'})} data-testid="child"></div>
23+
<div ${initComponent(data, {}, {id: 'the-child-id'})} data-testid="child"></div>
2424
`;
2525

2626
const test = await createTest({}, (data: any) => `
@@ -70,8 +70,8 @@ describe('LiveController parent -> child component tests', () => {
7070
it('sends a map of child fingerprints on re-render', async () => {
7171
const test = await createTest({}, (data: any) => `
7272
<div ${initComponent(data)}>
73-
<div ${initComponent({}, {id: 'the-child-id1', fingerprint: 'child-fingerprint1'})}>Child1</div>
74-
<div ${initComponent({}, {id: 'the-child-id2', fingerprint: 'child-fingerprint2'})}>Child2</div>
73+
<div ${initComponent({}, {}, {id: 'the-child-id1', fingerprint: 'child-fingerprint1'})}>Child1</div>
74+
<div ${initComponent({}, {}, {id: 'the-child-id2', fingerprint: 'child-fingerprint2'})}>Child2</div>
7575
</div>
7676
`);
7777

@@ -86,4 +86,100 @@ describe('LiveController parent -> child component tests', () => {
8686
test.component.render();
8787
await waitFor(() => expect(test.element).toHaveAttribute('busy'));
8888
});
89+
90+
it('removes missing child component on re-render', async () => {
91+
const test = await createTest({renderChild: true}, (data: any) => `
92+
<div ${initComponent(data)}>
93+
${data.renderChild
94+
? `<div ${initComponent({}, {}, {id: 'the-child-id'})} data-testid="child">Child Component</div>`
95+
: ''
96+
}
97+
</div>
98+
`);
99+
100+
test.expectsAjaxCall('get')
101+
.expectSentData(test.initialData)
102+
.serverWillChangeData((data: any) => {
103+
data.renderChild = false;
104+
})
105+
.init();
106+
107+
expect(test.element).toHaveTextContent('Child Component')
108+
expect(test.component.getChildren().size).toEqual(1);
109+
test.component.render();
110+
// wait for child to disappear
111+
await waitFor(() => expect(test.element).toHaveAttribute('busy'));
112+
await waitFor(() => expect(test.element).not.toHaveAttribute('busy'));
113+
expect(test.element).not.toHaveTextContent('Child Component')
114+
expect(test.component.getChildren().size).toEqual(0);
115+
});
116+
117+
it('adds new child component on re-render', async () => {
118+
const test = await createTest({renderChild: false}, (data: any) => `
119+
<div ${initComponent(data)}>
120+
${data.renderChild
121+
? `<div ${initComponent({}, {}, {id: 'the-child-id'})} data-testid="child">Child Component</div>`
122+
: ''
123+
}
124+
</div>
125+
`);
126+
127+
test.expectsAjaxCall('get')
128+
.expectSentData(test.initialData)
129+
.serverWillChangeData((data: any) => {
130+
data.renderChild = true;
131+
})
132+
.init();
133+
134+
expect(test.element).not.toHaveTextContent('Child Component')
135+
expect(test.component.getChildren().size).toEqual(0);
136+
test.component.render();
137+
// wait for child to disappear
138+
await waitFor(() => expect(test.element).toHaveAttribute('busy'));
139+
await waitFor(() => expect(test.element).not.toHaveAttribute('busy'));
140+
expect(test.element).toHaveTextContent('Child Component')
141+
expect(test.component.getChildren().size).toEqual(1);
142+
});
143+
144+
// it('existing child component that has no props is ignored', async () => {
145+
// const originalChild = `
146+
// <div ${initComponent({}, {}, {id: 'the-child-id'})} data-testid="child">
147+
// Child Component
148+
// </div>
149+
// `;
150+
// const updatedChild = `
151+
// <div ${initComponent({}, {}, {id: 'the-child-id'})} data-testid="child">
152+
// Child Component
153+
// </div>
154+
// `;
155+
//
156+
// const test = await createTest({useOriginalChild: true}, (data: any) => `
157+
// <div ${initComponent(data)}>
158+
// ${data.useUpdatedChild ? originalChild : updatedChild}
159+
// </div>
160+
// `);
161+
//
162+
// test.expectsAjaxCall('get')
163+
// .expectSentData(test.initialData)
164+
// .serverWillChangeData((data: any) => {
165+
// data.renderChild = true;
166+
// })
167+
// .init();
168+
//
169+
// expect(test.element).not.toHaveTextContent('Child Component')
170+
// expect(test.component.getChildren().size).toEqual(0);
171+
// test.component.render();
172+
// // wait for child to disappear
173+
// await waitFor(() => expect(test.element).toHaveAttribute('busy'));
174+
// await waitFor(() => expect(test.element).not.toHaveAttribute('busy'));
175+
// expect(test.element).toHaveTextContent('Child Component')
176+
// expect(test.component.getChildren().size).toEqual(1);
177+
// });
178+
179+
180+
// TODO: response comes back with existing child element but no props, child is untouched
181+
// TODO: response comes back with empty child element BUT with new fingerprint & props, those are pushed down onto the child, which will trigger a re-render
182+
// TODO: check above situation mixing element types - e.g. span vs div
183+
// TODO: check that data-live-id could be used to remove old component and use new component entirely, with fresh data
184+
// TODO: multiple children, even if they change position, are correctly handled
89185
});

src/LiveComponent/assets/test/controller/csrf.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('LiveController CSRF Tests', () => {
1919

2020
it('Sends the CSRF token on an action', async () => {
2121
const test = await createTest({ isSaved: 0 }, (data: any) => `
22-
<div ${initComponent(data, { csrf: '123TOKEN' })}>
22+
<div ${initComponent(data, {}, { csrf: '123TOKEN' })}>
2323
${data.isSaved ? 'Saved' : ''}
2424
<button
2525
data-action="live#action"

src/LiveComponent/assets/test/controller/model.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ describe('LiveController data-model Tests', () => {
5050

5151
it('updates the data without re-rendering if "norender" is used', async () => {
5252
const test = await createTest({ name: 'Ryan' }, (data: any) => `
53-
<div ${initComponent(data, { debounce: 1 })}>
53+
<div ${initComponent(data, {}, { debounce: 1 })}>
5454
<input
5555
data-model="norender|name"
5656
value="${data.name}"
@@ -74,7 +74,7 @@ describe('LiveController data-model Tests', () => {
7474

7575
it('waits to update data and rerender until change event with on(change)', async () => {
7676
const test = await createTest({ name: 'Ryan' }, (data: any) => `
77-
<div ${initComponent(data, { debounce: 1 })}>
77+
<div ${initComponent(data, {}, { debounce: 1 })}>
7878
<input
7979
data-model="on(change)|name"
8080
value="${data.name}"

src/LiveComponent/assets/test/controller/poll.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ describe('LiveController polling Tests', () => {
187187

188188
it('waits to send the request if request is already happening', async () => {
189189
const test = await createTest({ renderCount: 0, name: 'Ryan' }, (data: any) => `
190-
<div ${initComponent(data, { debounce: 1 })} data-poll="delay(50)|$render">
190+
<div ${initComponent(data, {}, { debounce: 1 })} data-poll="delay(50)|$render">
191191
<input
192192
data-model="name"
193193
value="${data.name}"

src/LiveComponent/assets/test/controller/render.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('LiveController rendering Tests', () => {
4545

4646
it('conserves the value of model field that was modified after a render request', async () => {
4747
const test = await createTest({ title: 'greetings', comment: '' }, (data: any) => `
48-
<div ${initComponent(data, { debounce: 1 })}>
48+
<div ${initComponent(data, {}, { debounce: 1 })}>
4949
<input data-model="title" value="${data.title}">
5050
<!--
5151
norender for comment to avoid triggering a 2nd Ajax call.
@@ -111,7 +111,7 @@ describe('LiveController rendering Tests', () => {
111111

112112
it('conserves the value of an unmapped field that was modified after a render request', async () => {
113113
const test = await createTest({ title: 'greetings' }, (data: any) => `
114-
<div ${initComponent(data, { debounce: 1 })}>
114+
<div ${initComponent(data, {}, { debounce: 1 })}>
115115
<input data-model="title" value="${data.title}">
116116
<!-- An unmapped field -->
117117
<textarea></textarea>
@@ -275,7 +275,7 @@ describe('LiveController rendering Tests', () => {
275275

276276
it('waits for the previous request to finish & batches changes', async () => {
277277
const test = await createTest({ title: 'greetings', contents: '' }, (data: any) => `
278-
<div ${initComponent(data, { debounce: 1 })}>
278+
<div ${initComponent(data, {}, { debounce: 1 })}>
279279
<input data-model="title" value="${data.title}">
280280
<textarea data-model="contents">${data.contents}</textarea>
281281
@@ -314,7 +314,7 @@ describe('LiveController rendering Tests', () => {
314314

315315
it('batches re-render requests together that occurred during debounce', async () => {
316316
const test = await createTest({ title: 'greetings', contents: '' }, (data: any) => `
317-
<div ${initComponent(data, { debounce: 50 })}>
317+
<div ${initComponent(data, {}, { debounce: 50 })}>
318318
<input data-model="title" value="${data.title}">
319319
<textarea data-model="contents">${data.contents}</textarea>
320320

src/LiveComponent/assets/test/tools.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,12 +288,16 @@ class MockedAjaxCall {
288288
const params = new URLSearchParams(paramsData);
289289
if (createMatchForShowingError) {
290290
// simplified version for error reporting
291-
matcherObject.url = `?${params.toString()}`;
291+
matcherObject.url = `(approximation) ?${params.toString()}`;
292292
} else {
293293
matcherObject.functionMatcher = (url: string) => {
294294
const actualUrl = new URL(url);
295295
const actualParams = new URLSearchParams(actualUrl.search);
296-
actualParams.delete('updatedModels');
296+
actualParams.delete('updatedModels[]');
297+
// if we're not expecting specific child fingerprints, ignore them
298+
if (!paramsData.childrenFingerprints) {
299+
actualParams.delete('childrenFingerprints');
300+
}
297301

298302
return actualParams.toString() === params.toString();
299303
};
@@ -414,11 +418,12 @@ const dataToJsonAttribute = (data: any): string => {
414418
return matches[1]
415419
}
416420

417-
export function initComponent(data: any, controllerValues: any = {}) {
421+
export function initComponent(data: any, props: any = {}, controllerValues: any = {}) {
418422
return `
419423
data-controller="live"
420424
data-live-url-value="http://localhost/components/_test_component_${Math.round(Math.random() * 1000)}"
421425
data-live-data-value="${dataToJsonAttribute(data)}"
426+
data-live-props-value="${dataToJsonAttribute(props)}"
422427
${controllerValues.debounce ? `data-live-debounce-value="${controllerValues.debounce}"` : ''}
423428
${controllerValues.csrf ? `data-live-csrf-value="${controllerValues.csrf}"` : ''}
424429
${controllerValues.id ? `data-live-id-value="${controllerValues.id}"` : ''}

0 commit comments

Comments
 (0)