mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(core): Add four tests and fix code to make tests pass. (#55747)
The first test asserts that bubbling does not work right now. The second asserts that stopPropagation works, which should pass when test #1 passes too. The third test asserts properties about the events passed to the event handler. THe fourth test asserts that mouse events do not translate to jsaction nor help emit the jsaction binary. This required a change in code to make this pass. PR Close #55747
This commit is contained in:
committed by
Andrew Scott
parent
ef029b8d00
commit
629b255ddb
@@ -142,6 +142,14 @@ export function collectDomEventsInfo(
|
||||
continue;
|
||||
}
|
||||
const name: string = firstParam;
|
||||
if (
|
||||
name === 'mouseenter' ||
|
||||
name === 'mouseleave' ||
|
||||
name === 'pointerenter' ||
|
||||
name === 'pointerleave'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
eventTypesToReplay.add(name);
|
||||
const listenerElement = unwrapRNode(lView[secondParam]) as any as Element;
|
||||
i++; // move the cursor to the next position (location of the listener idx)
|
||||
|
||||
@@ -145,14 +145,6 @@ function insertEventRecordScript(
|
||||
const captureEventTypes = [];
|
||||
const eventTypes = [];
|
||||
for (const eventType of events) {
|
||||
if (
|
||||
eventType === 'mouseenter' ||
|
||||
eventType === 'mouseleave' ||
|
||||
eventType === 'pointerenter' ||
|
||||
eventType === 'pointerleave'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
eventType === 'focus' ||
|
||||
eventType === 'blur' ||
|
||||
|
||||
@@ -25,6 +25,7 @@ import {getAppContents, hydrate, render as renderHtml, resetTViewsFor} from './d
|
||||
* event dispatch (JSAction) logic.
|
||||
*/
|
||||
const EVENT_DISPATCH_SCRIPT = `<script type="text/javascript" id="${EVENT_DISPATCH_SCRIPT_ID}"></script>`;
|
||||
const DEFAULT_DOCUMENT = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
|
||||
|
||||
/** Checks whether event dispatch script is present in the generated HTML */
|
||||
function hasEventDispatchScript(content: string) {
|
||||
@@ -37,88 +38,78 @@ function hasJSActionAttrs(content: string) {
|
||||
}
|
||||
|
||||
describe('event replay', () => {
|
||||
let doc: Document;
|
||||
const originalDocument = globalThis.document;
|
||||
const originalWindow = globalThis.window;
|
||||
|
||||
beforeAll(async () => {
|
||||
globalThis.window = globalThis as unknown as Window & typeof globalThis;
|
||||
await import('@angular/core/primitives/event-dispatch/contract_bundle_min.js' as string);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
if (getPlatform()) destroyPlatform();
|
||||
doc = TestBed.inject(DOCUMENT);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
globalThis.window = originalWindow;
|
||||
globalThis.document = originalDocument;
|
||||
destroyPlatform();
|
||||
});
|
||||
|
||||
describe('event replay', () => {
|
||||
/**
|
||||
* This renders the application with server side rendering logic.
|
||||
*
|
||||
* @param component the test component to be rendered
|
||||
* @param doc the document
|
||||
* @param envProviders the environment providers
|
||||
* @returns a promise containing the server rendered app as a string
|
||||
*/
|
||||
async function ssr(
|
||||
component: Type<unknown>,
|
||||
options: {doc?: string; enableEventReplay?: boolean; hydrationDisabled?: boolean} = {},
|
||||
): Promise<string> {
|
||||
const {
|
||||
enableEventReplay = true,
|
||||
hydrationDisabled,
|
||||
doc = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`,
|
||||
} = options;
|
||||
afterEach(() => {
|
||||
doc.body.textContent = '';
|
||||
});
|
||||
|
||||
const hydrationProviders = hydrationDisabled
|
||||
? []
|
||||
: enableEventReplay
|
||||
? provideClientHydration(withEventReplay())
|
||||
: provideClientHydration();
|
||||
/**
|
||||
* This renders the application with server side rendering logic.
|
||||
*
|
||||
* @param component the test component to be rendered
|
||||
* @param doc the document
|
||||
* @param envProviders the environment providers
|
||||
* @returns a promise containing the server rendered app as a string
|
||||
*/
|
||||
async function ssr(
|
||||
component: Type<unknown>,
|
||||
options: {doc?: string; enableEventReplay?: boolean; hydrationDisabled?: boolean} = {},
|
||||
): Promise<string> {
|
||||
const {enableEventReplay = true, hydrationDisabled, doc = DEFAULT_DOCUMENT} = options;
|
||||
|
||||
const bootstrap = () =>
|
||||
bootstrapApplication(component, {
|
||||
providers: [provideServerRendering(), hydrationProviders],
|
||||
});
|
||||
const hydrationProviders = hydrationDisabled
|
||||
? []
|
||||
: enableEventReplay
|
||||
? provideClientHydration(withEventReplay())
|
||||
: provideClientHydration();
|
||||
|
||||
return renderApplication(bootstrap, {
|
||||
document: doc,
|
||||
const bootstrap = () =>
|
||||
bootstrapApplication(component, {
|
||||
providers: [provideServerRendering(), hydrationProviders],
|
||||
});
|
||||
}
|
||||
|
||||
describe('server rendering', () => {
|
||||
let doc: Document;
|
||||
const originalDocument = globalThis.document;
|
||||
const originalWindow = globalThis.window;
|
||||
return renderApplication(bootstrap, {
|
||||
document: doc,
|
||||
});
|
||||
}
|
||||
|
||||
function render(doc: Document, html: string) {
|
||||
renderHtml(doc, html);
|
||||
globalThis.document = doc;
|
||||
const scripts = doc.getElementsByTagName('script');
|
||||
for (const script of Array.from(scripts)) {
|
||||
if (script?.textContent?.startsWith('window.__jsaction_bootstrap')) {
|
||||
eval(script.textContent);
|
||||
}
|
||||
}
|
||||
function render(doc: Document, html: string) {
|
||||
renderHtml(doc, html);
|
||||
globalThis.document = doc;
|
||||
const scripts = doc.getElementsByTagName('script');
|
||||
for (const script of Array.from(scripts)) {
|
||||
if (script?.textContent?.startsWith('window.__jsaction_bootstrap')) {
|
||||
eval(script.textContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
globalThis.window = globalThis as unknown as Window & typeof globalThis;
|
||||
await import('@angular/core/primitives/event-dispatch/contract_bundle_min.js' as string);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
doc = TestBed.inject(DOCUMENT);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
doc.body.textContent = '';
|
||||
});
|
||||
afterAll(() => {
|
||||
globalThis.window = originalWindow;
|
||||
globalThis.document = originalDocument;
|
||||
});
|
||||
it('should serialize event types to be listened to and jsaction attribute', async () => {
|
||||
const clickSpy = jasmine.createSpy('onClick');
|
||||
const focusSpy = jasmine.createSpy('onFocus');
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: `
|
||||
it('should serialize event types to be listened to and jsaction attribute', async () => {
|
||||
const clickSpy = jasmine.createSpy('onClick');
|
||||
const focusSpy = jasmine.createSpy('onFocus');
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: `
|
||||
<div (click)="onClick()" id="click-element">
|
||||
<div id="focus-container">
|
||||
<div id="focus-action-element" (focus)="onFocus()">
|
||||
@@ -127,172 +118,282 @@ describe('event replay', () => {
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick = clickSpy;
|
||||
onFocus = focusSpy;
|
||||
}
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick = clickSpy;
|
||||
onFocus = focusSpy;
|
||||
}
|
||||
const html = await ssr(SimpleComponent);
|
||||
const ssrContents = getAppContents(html);
|
||||
|
||||
const docContents = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
|
||||
const html = await ssr(SimpleComponent, {doc: docContents});
|
||||
const ssrContents = getAppContents(html);
|
||||
expect(ssrContents).toContain(
|
||||
`<script>window.__jsaction_bootstrap('ngContracts', document.body, "ng", ["click"],["focus"]);</script>`,
|
||||
);
|
||||
render(doc, ssrContents);
|
||||
const el = doc.getElementById('click-element')!;
|
||||
const button = doc.getElementById('focus-target-element')!;
|
||||
const clickEvent = new CustomEvent('click', {bubbles: true});
|
||||
el.dispatchEvent(clickEvent);
|
||||
const focusEvent = new CustomEvent('focus');
|
||||
button.dispatchEvent(focusEvent);
|
||||
expect(clickSpy).not.toHaveBeenCalled();
|
||||
expect(focusSpy).not.toHaveBeenCalled();
|
||||
resetTViewsFor(SimpleComponent);
|
||||
const appRef = await hydrate(doc, SimpleComponent, {
|
||||
hydrationFeatures: [withEventReplay()],
|
||||
});
|
||||
appRef.tick();
|
||||
expect(clickSpy).toHaveBeenCalled();
|
||||
expect(focusSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
render(doc, ssrContents);
|
||||
const el = doc.getElementById('click-element')!;
|
||||
const button = doc.getElementById('focus-target-element')!;
|
||||
const clickEvent = new CustomEvent('click', {bubbles: true});
|
||||
el.dispatchEvent(clickEvent);
|
||||
const focusEvent = new CustomEvent('focus');
|
||||
button.dispatchEvent(focusEvent);
|
||||
expect(clickSpy).not.toHaveBeenCalled();
|
||||
expect(focusSpy).not.toHaveBeenCalled();
|
||||
resetTViewsFor(SimpleComponent);
|
||||
const appRef = await hydrate(doc, SimpleComponent, {
|
||||
hydrationFeatures: [withEventReplay()],
|
||||
});
|
||||
appRef.tick();
|
||||
expect(clickSpy).toHaveBeenCalled();
|
||||
expect(focusSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should remove jsaction attributes, but continue listening to events.', async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: `
|
||||
it('should remove jsaction attributes, but continue listening to events.', async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: `
|
||||
<div (click)="onClick()" id="1">
|
||||
<div (click)="onClick()" id="2"></div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick() {}
|
||||
}
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick() {}
|
||||
}
|
||||
|
||||
const docContents = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
|
||||
const html = await ssr(SimpleComponent, {doc: docContents});
|
||||
const ssrContents = getAppContents(html);
|
||||
render(doc, ssrContents);
|
||||
const el = doc.getElementById('1')!;
|
||||
expect(el.hasAttribute('jsaction')).toBeTrue();
|
||||
expect((el.firstChild as Element).hasAttribute('jsaction')).toBeTrue();
|
||||
resetTViewsFor(SimpleComponent);
|
||||
const appRef = await hydrate(doc, SimpleComponent, {
|
||||
hydrationFeatures: [withEventReplay()],
|
||||
});
|
||||
appRef.tick();
|
||||
expect(el.hasAttribute('jsaction')).toBeFalse();
|
||||
expect((el.firstChild as Element).hasAttribute('jsaction')).toBeFalse();
|
||||
});
|
||||
const docContents = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
|
||||
const html = await ssr(SimpleComponent, {doc: docContents});
|
||||
const ssrContents = getAppContents(html);
|
||||
render(doc, ssrContents);
|
||||
const el = doc.getElementById('1')!;
|
||||
expect(el.hasAttribute('jsaction')).toBeTrue();
|
||||
expect((el.firstChild as Element).hasAttribute('jsaction')).toBeTrue();
|
||||
resetTViewsFor(SimpleComponent);
|
||||
const appRef = await hydrate(doc, SimpleComponent, {
|
||||
hydrationFeatures: [withEventReplay()],
|
||||
});
|
||||
appRef.tick();
|
||||
expect(el.hasAttribute('jsaction')).toBeFalse();
|
||||
expect((el.firstChild as Element).hasAttribute('jsaction')).toBeFalse();
|
||||
});
|
||||
|
||||
it(`should add 'nonce' attribute to event record script when 'ngCspNonce' is provided`, async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: `
|
||||
it(`should add 'nonce' attribute to event record script when 'ngCspNonce' is provided`, async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: `
|
||||
<div (click)="onClick()">
|
||||
<div (blur)="onClick()"></div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick() {}
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick() {}
|
||||
}
|
||||
|
||||
const doc =
|
||||
`<html><head></head><body>${EVENT_DISPATCH_SCRIPT}` +
|
||||
`<app ngCspNonce="{{nonce}}"></app></body></html>`;
|
||||
const html = await ssr(SimpleComponent, {doc});
|
||||
expect(getAppContents(html)).toContain('<script nonce="{{nonce}}">window.__jsaction_bootstrap');
|
||||
});
|
||||
|
||||
describe('bubbling behavior', () => {
|
||||
it('should propagate events', async () => {
|
||||
const onClickSpy = jasmine.createSpy();
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: `
|
||||
<div id="top" (click)="onClick()">
|
||||
<div id="bottom" (click)="onClick()"></div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick = onClickSpy;
|
||||
}
|
||||
const docContents = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
|
||||
const html = await ssr(SimpleComponent, {doc: docContents});
|
||||
const ssrContents = getAppContents(html);
|
||||
render(doc, ssrContents);
|
||||
resetTViewsFor(SimpleComponent);
|
||||
const bottomEl = doc.getElementById('bottom')!;
|
||||
bottomEl.click();
|
||||
const appRef = await hydrate(doc, SimpleComponent, {
|
||||
hydrationFeatures: [withEventReplay()],
|
||||
});
|
||||
appRef.tick();
|
||||
// This is a bug
|
||||
expect(onClickSpy).toHaveBeenCalledTimes(1);
|
||||
onClickSpy.calls.reset();
|
||||
bottomEl.click();
|
||||
expect(onClickSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should not propagate events if stopPropagation is called', async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: `
|
||||
<div id="top" (click)="onClick($event)">
|
||||
<div id="bottom" (click)="onClick($event)"></div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick(e: Event) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
const doc =
|
||||
`<html><head></head><body>${EVENT_DISPATCH_SCRIPT}` +
|
||||
`<app ngCspNonce="{{nonce}}"></app></body></html>`;
|
||||
const html = await ssr(SimpleComponent, {doc});
|
||||
expect(getAppContents(html)).toContain(
|
||||
'<script nonce="{{nonce}}">window.__jsaction_bootstrap',
|
||||
);
|
||||
}
|
||||
const onClickSpy = spyOn(SimpleComponent.prototype, 'onClick').and.callThrough();
|
||||
const docContents = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
|
||||
const html = await ssr(SimpleComponent, {doc: docContents});
|
||||
const ssrContents = getAppContents(html);
|
||||
render(doc, ssrContents);
|
||||
resetTViewsFor(SimpleComponent);
|
||||
const bottomEl = doc.getElementById('bottom')!;
|
||||
bottomEl.click();
|
||||
const appRef = await hydrate(doc, SimpleComponent, {
|
||||
hydrationFeatures: [withEventReplay()],
|
||||
});
|
||||
appRef.tick();
|
||||
expect(onClickSpy).toHaveBeenCalledTimes(1);
|
||||
onClickSpy.calls.reset();
|
||||
bottomEl.click();
|
||||
expect(onClickSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe('event dispatch script', () => {
|
||||
it('should not be present on a page when hydration is disabled', async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: '<input (click)="onClick()" />',
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick() {}
|
||||
}
|
||||
|
||||
const doc = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
|
||||
const html = await ssr(SimpleComponent, {doc, hydrationDisabled: true});
|
||||
const ssrContents = getAppContents(html);
|
||||
|
||||
expect(hasJSActionAttrs(ssrContents)).toBeFalse();
|
||||
expect(hasEventDispatchScript(ssrContents)).toBeFalse();
|
||||
});
|
||||
|
||||
it('should not be present on a page if there are no events to replay', async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: 'Some text',
|
||||
})
|
||||
class SimpleComponent {}
|
||||
|
||||
const doc = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
|
||||
const html = await ssr(SimpleComponent, {doc});
|
||||
const ssrContents = getAppContents(html);
|
||||
|
||||
expect(hasJSActionAttrs(ssrContents)).toBeFalse();
|
||||
expect(hasEventDispatchScript(ssrContents)).toBeFalse();
|
||||
});
|
||||
|
||||
it('should not be present on a page where event replay is not enabled', async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: '<input (click)="onClick()" />',
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick() {}
|
||||
}
|
||||
|
||||
const doc = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
|
||||
const html = await ssr(SimpleComponent, {doc, enableEventReplay: false});
|
||||
const ssrContents = getAppContents(html);
|
||||
|
||||
// Expect that there are no JSAction artifacts in the HTML
|
||||
// (even though there are events in a template), since event
|
||||
// replay is disabled in the config.
|
||||
expect(hasJSActionAttrs(ssrContents)).toBeFalse();
|
||||
expect(hasEventDispatchScript(ssrContents)).toBeFalse();
|
||||
});
|
||||
|
||||
it('should be retained if there are events to replay', async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: '<input (click)="onClick()" />',
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick() {}
|
||||
}
|
||||
|
||||
const doc = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
|
||||
const html = await ssr(SimpleComponent, {doc});
|
||||
const ssrContents = getAppContents(html);
|
||||
|
||||
expect(hasJSActionAttrs(ssrContents)).toBeTrue();
|
||||
expect(hasEventDispatchScript(ssrContents)).toBeTrue();
|
||||
|
||||
// Verify that inlined event delegation script goes first and
|
||||
// event contract setup goes second (since it uses some code from
|
||||
// the inlined script).
|
||||
expect(ssrContents).toContain(
|
||||
`<script type="text/javascript" id="ng-event-dispatch-contract"></script>` +
|
||||
`<script>window.__jsaction_bootstrap('ngContracts', document.body, "ng", ["click"]);</script>`,
|
||||
);
|
||||
});
|
||||
it('should not have differences in event fields', async () => {
|
||||
let currentEvent!: Event;
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: `
|
||||
<div id="top" (click)="onClick($event)">
|
||||
<div id="bottom" (click)="onClick($event)"></div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick(event: Event) {
|
||||
currentEvent = event;
|
||||
}
|
||||
}
|
||||
const docContents = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
|
||||
const html = await ssr(SimpleComponent, {doc: docContents});
|
||||
const ssrContents = getAppContents(html);
|
||||
render(doc, ssrContents);
|
||||
resetTViewsFor(SimpleComponent);
|
||||
const bottomEl = doc.getElementById('bottom')!;
|
||||
bottomEl.click();
|
||||
const appRef = await hydrate(doc, SimpleComponent, {
|
||||
hydrationFeatures: [withEventReplay()],
|
||||
});
|
||||
appRef.tick();
|
||||
const replayedEvent = currentEvent;
|
||||
bottomEl.click();
|
||||
appRef.tick();
|
||||
const normalEvent = currentEvent;
|
||||
expect(replayedEvent).not.toBe(normalEvent);
|
||||
expect(replayedEvent.target).toBe(normalEvent.target);
|
||||
expect(replayedEvent.currentTarget).toBe(normalEvent.currentTarget);
|
||||
expect(replayedEvent.composedPath).toBe(normalEvent.composedPath);
|
||||
expect(replayedEvent.eventPhase).toBe(normalEvent.eventPhase);
|
||||
});
|
||||
});
|
||||
|
||||
describe('event dispatch script', () => {
|
||||
it('should not be present on a page when hydration is disabled', async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: '<input (click)="onClick()" />',
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick() {}
|
||||
}
|
||||
|
||||
const doc = `<html><head></head><body>${EVENT_DISPATCH_SCRIPT}<app></app></body></html>`;
|
||||
const html = await ssr(SimpleComponent, {doc, hydrationDisabled: true});
|
||||
const ssrContents = getAppContents(html);
|
||||
|
||||
expect(hasJSActionAttrs(ssrContents)).toBeFalse();
|
||||
expect(hasEventDispatchScript(ssrContents)).toBeFalse();
|
||||
});
|
||||
|
||||
it('should not be present on a page if there are no events to replay', async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: 'Some text',
|
||||
})
|
||||
class SimpleComponent {}
|
||||
|
||||
const html = await ssr(SimpleComponent);
|
||||
const ssrContents = getAppContents(html);
|
||||
|
||||
expect(hasJSActionAttrs(ssrContents)).toBeFalse();
|
||||
expect(hasEventDispatchScript(ssrContents)).toBeFalse();
|
||||
});
|
||||
|
||||
it('should not replay mouse events', async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: '<div (mouseenter)="doThing()"><div>',
|
||||
})
|
||||
class SimpleComponent {
|
||||
doThing() {}
|
||||
}
|
||||
|
||||
const html = await ssr(SimpleComponent);
|
||||
const ssrContents = getAppContents(html);
|
||||
|
||||
expect(hasJSActionAttrs(ssrContents)).toBeFalse();
|
||||
expect(hasEventDispatchScript(ssrContents)).toBeFalse();
|
||||
});
|
||||
|
||||
it('should not be present on a page where event replay is not enabled', async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: '<input (click)="onClick()" />',
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick() {}
|
||||
}
|
||||
|
||||
const html = await ssr(SimpleComponent, {enableEventReplay: false});
|
||||
const ssrContents = getAppContents(html);
|
||||
|
||||
// Expect that there are no JSAction artifacts in the HTML
|
||||
// (even though there are events in a template), since event
|
||||
// replay is disabled in the config.
|
||||
expect(hasJSActionAttrs(ssrContents)).toBeFalse();
|
||||
expect(hasEventDispatchScript(ssrContents)).toBeFalse();
|
||||
});
|
||||
|
||||
it('should be retained if there are events to replay', async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app',
|
||||
template: '<input (click)="onClick()" />',
|
||||
})
|
||||
class SimpleComponent {
|
||||
onClick() {}
|
||||
}
|
||||
|
||||
const html = await ssr(SimpleComponent);
|
||||
const ssrContents = getAppContents(html);
|
||||
|
||||
expect(hasJSActionAttrs(ssrContents)).toBeTrue();
|
||||
expect(hasEventDispatchScript(ssrContents)).toBeTrue();
|
||||
|
||||
// Verify that inlined event delegation script goes first and
|
||||
// event contract setup goes second (since it uses some code from
|
||||
// the inlined script).
|
||||
expect(ssrContents).toContain(
|
||||
`<script type="text/javascript" id="ng-event-dispatch-contract"></script>` +
|
||||
`<script>window.__jsaction_bootstrap('ngContracts', document.body, "ng", ["click"]);</script>`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user