| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import {html, render} from '../ui/lit/lit.js'; |
| |
| import {renderElementIntoDOM} from './DOMHelpers.js'; |
| import {TEXT_NODE, withMutations, withNoMutations} from './MutationHelpers.js'; |
| |
| /** |
| * Needed because assert.throws from chai does not work async. |
| */ |
| async function assertThrowsAsync(fn: () => Promise<void>, errorMessage: string) { |
| let caught = false; |
| try { |
| await fn(); |
| } catch (e) { |
| caught = true; |
| assert.strictEqual(e.message, errorMessage); |
| } |
| |
| if (!caught) { |
| assert.fail('Expected error but got none.'); |
| } |
| } |
| |
| async function assertNotThrowsAsync(fn: () => Promise<void>) { |
| let errorMessage = ''; |
| try { |
| await fn(); |
| } catch (e) { |
| errorMessage = e.message; |
| } |
| |
| if (errorMessage) { |
| assert.fail(`Expected no error but got:\n${errorMessage}`); |
| } |
| } |
| |
| describe('MutationHelpers', () => { |
| describe('withMutations', () => { |
| it('fails if there are no mutations', async () => { |
| const div = document.createElement('div'); |
| await assertThrowsAsync(async () => { |
| await withMutations( |
| [{ |
| target: 'div', |
| }], |
| div, () => {}); |
| }, 'Expected at least one mutation for ADD/REMOVE div, but got 0'); |
| }); |
| |
| it('allows up to 10 mutations unless specified', async () => { |
| const div = document.createElement('div'); |
| renderElementIntoDOM(div); |
| await assertNotThrowsAsync(async () => { |
| await withMutations( |
| [{ |
| target: 'div', |
| }], |
| div, () => { |
| for (let i = 0; i < 10; i++) { |
| div.appendChild(document.createElement('div')); |
| } |
| }); |
| }); |
| }); |
| |
| it('errors if there are >10 mutations', async () => { |
| const div = document.createElement('div'); |
| renderElementIntoDOM(div); |
| await assertThrowsAsync(async () => { |
| await withMutations( |
| [{ |
| target: 'div', |
| }], |
| div, () => { |
| for (let i = 0; i < 11; i++) { |
| div.appendChild(document.createElement('div')); |
| } |
| }); |
| }, 'Expected no more than 10 mutations for ADD/REMOVE div, but got 11'); |
| }); |
| |
| it('lets the user provide the max', async () => { |
| const div = document.createElement('div'); |
| renderElementIntoDOM(div); |
| await assertThrowsAsync(async () => { |
| await withMutations( |
| [{ |
| target: 'div', |
| max: 5, |
| }], |
| div, () => { |
| for (let i = 0; i < 6; i++) { |
| div.appendChild(document.createElement('div')); |
| } |
| }); |
| }, 'Expected no more than 5 mutations for ADD/REMOVE div, but got 6'); |
| }); |
| |
| it('supports a max of 0', async () => { |
| const div = document.createElement('div'); |
| renderElementIntoDOM(div); |
| await assertThrowsAsync(async () => { |
| await withMutations( |
| [{ |
| target: 'div', |
| max: 0, |
| }], |
| div, () => { |
| div.appendChild(document.createElement('div')); |
| }); |
| }, 'Expected no more than 0 mutations for ADD/REMOVE div, but got 1'); |
| }); |
| |
| it('supports checking multiple expected mutations', async () => { |
| const div = document.createElement('div'); |
| renderElementIntoDOM(div); |
| await assertThrowsAsync(async () => { |
| await withMutations( |
| [ |
| { |
| target: 'div', |
| max: 1, |
| }, |
| {target: 'span', max: 0}, |
| ], |
| div, () => { |
| div.appendChild(document.createElement('div')); |
| div.appendChild(document.createElement('span')); |
| }); |
| }, 'Expected no more than 0 mutations for ADD/REMOVE span, but got 1'); |
| }); |
| |
| it('errors if other unexpected mutations occur', async () => { |
| const div = document.createElement('div'); |
| renderElementIntoDOM(div); |
| await assertThrowsAsync(async () => { |
| await withMutations( |
| [{ |
| target: 'div', |
| max: 1, |
| }], |
| div, () => { |
| // this is OK as we are expecting one div mutation |
| div.appendChild(document.createElement('div')); |
| // not OK - we have not declared any span mutations |
| div.appendChild(document.createElement('span')); |
| }); |
| }, 'Additional unexpected mutations were detected:\nspan: 1 addition'); |
| }); |
| |
| it('lets you declare any expected text updates', async () => { |
| const div = document.createElement('div'); |
| const renderList = (list: string[]) => { |
| render(html`${list.map(l => html`<span>${l}</span>`)}`, div, {host: this}); |
| }; |
| |
| renderElementIntoDOM(div); |
| renderList(['a', 'b']); |
| |
| await assertNotThrowsAsync(async () => { |
| await withMutations( |
| [ |
| { |
| target: 'div', |
| }, |
| {target: TEXT_NODE}, |
| ], |
| div, div => { |
| renderList(['b', 'a']); |
| div.appendChild(document.createElement('div')); |
| }); |
| }); |
| }); |
| |
| it('fails if there are undeclared text updates', async () => { |
| const div = document.createElement('div'); |
| const renderList = (list: string[]) => { |
| render(html`${list.map(l => html`<span>${l}</span>`)}`, div, {host: this}); |
| }; |
| |
| renderElementIntoDOM(div); |
| renderList(['a', 'b']); |
| |
| await assertThrowsAsync(async () => { |
| await withMutations( |
| [{ |
| target: 'div', |
| }], |
| div, div => { |
| renderList(['b', 'a']); |
| div.appendChild(document.createElement('div')); |
| }); |
| }, 'Additional unexpected mutations were detected:\nTEXT_NODE: 2 updates'); |
| }); |
| }); |
| |
| describe('withNoMutations', () => { |
| it('fails if there are DOM additions', async () => { |
| const div = document.createElement('div'); |
| renderElementIntoDOM(div); |
| await assertThrowsAsync(async () => { |
| await withNoMutations(div, element => { |
| const child = document.createElement('span'); |
| element.appendChild(child); |
| }); |
| }, 'Expected no mutations, but got 1: \nspan: 1 addition'); |
| }); |
| |
| it('fails if there are DOM removals', async () => { |
| const div = document.createElement('div'); |
| const child = document.createElement('span'); |
| div.appendChild(child); |
| renderElementIntoDOM(div); |
| |
| await assertThrowsAsync(async () => { |
| await withNoMutations(div, element => { |
| element.removeChild(child); |
| }); |
| }, 'Expected no mutations, but got 1: \nspan: 1 removal'); |
| }); |
| |
| it('correctly displays multiple unexpected mutations', async () => { |
| const div = document.createElement('div'); |
| renderElementIntoDOM(div); |
| await assertThrowsAsync(async () => { |
| await withNoMutations(div, element => { |
| const child = document.createElement('span'); |
| element.appendChild(child); |
| element.removeChild(child); |
| element.appendChild(document.createElement('p')); |
| element.appendChild(document.createElement('p')); |
| element.appendChild(document.createElement('p')); |
| }); |
| }, 'Expected no mutations, but got 5: \nspan: 1 addition, 1 removal\np: 3 additions'); |
| }); |
| |
| it('fails if there are text re-orderings', async () => { |
| const div = document.createElement('div'); |
| const renderList = (list: string[]) => { |
| render(html`${list.map(l => html`<span>${l}</span>`)}`, div, {host: this}); |
| }; |
| |
| renderElementIntoDOM(div); |
| renderList(['a', 'b']); |
| |
| await assertThrowsAsync(async () => { |
| await withNoMutations(div, () => { |
| renderList(['b', 'a']); |
| }); |
| }, 'Expected no mutations, but got 2: \nTEXT_NODE: 2 updates'); |
| }); |
| |
| it('fails if there are text re-orderings and DOM additions', async () => { |
| const div = document.createElement('div'); |
| const renderList = (list: string[]) => { |
| render(html`${list.map(l => html`<span>${l}</span>`)}`, div, {host: this}); |
| }; |
| |
| renderElementIntoDOM(div); |
| renderList(['a', 'b']); |
| |
| await assertThrowsAsync(async () => { |
| await withNoMutations(div, div => { |
| renderList(['b', 'a']); |
| div.appendChild(document.createElement('ul')); |
| }); |
| }, 'Expected no mutations, but got 3: \nTEXT_NODE: 2 updates\nul: 1 addition'); |
| }); |
| }); |
| }); |