blob: 17c64d74a5ed7dedd8188897ccb3bc59b76a2f8e [file]
/**
* @license
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import * as sinon from 'sinon';
import '../test/common-test-setup';
import './gr-app';
import {getAppContext} from '../services/app-context';
import {assert, fixture, html} from '@open-wc/testing';
import {queryAndAssert, stubElement, stubRestApi} from '../test/test-utils';
import {GrApp} from './gr-app';
import {
createAppElementSearchViewParams,
createChangeViewState,
createPreferences,
createServerInfo,
} from '../test/test-data-generators';
import {GrAppElement} from './gr-app-element';
import {GrRouter, routerToken} from './core/gr-router/gr-router';
import {resolve} from '../models/dependency';
import {removeRequestDependencyListener} from '../test/common-test-setup';
import {ReactiveElement} from 'lit';
suite('gr-app callback tests', () => {
const requestUpdateStub = sinon.stub(
ReactiveElement.prototype,
'requestUpdate'
);
const dispatchLocationChangeEventSpy = sinon.spy(
GrRouter.prototype,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<any>'dispatchLocationChangeEvent'
);
setup(async () => {
await fixture<GrApp>(html`<gr-app id="app"></gr-app>`);
});
test("requestUpdate in reactive-element is called after dispatching 'location-change' event in gr-router", () => {
dispatchLocationChangeEventSpy();
assert.isTrue(requestUpdateStub.calledOnce);
});
});
suite('gr-app tests', () => {
let grApp: GrApp;
const config = createServerInfo();
let appStartedStub: sinon.SinonStub;
let routerStartStub: sinon.SinonStub;
setup(async () => {
appStartedStub = sinon.stub(getAppContext().reportingService, 'appStarted');
stubElement('gr-account-dropdown', 'getTopContent');
routerStartStub = sinon.stub(GrRouter.prototype, 'start');
stubRestApi('getAccount').returns(Promise.resolve(undefined));
stubRestApi('getAccountCapabilities').returns(Promise.resolve({}));
stubRestApi('getConfig').returns(Promise.resolve(config));
stubRestApi('getPreferences').returns(Promise.resolve(createPreferences()));
stubRestApi('getVersion').returns(Promise.resolve('42'));
stubRestApi('probePath').returns(Promise.resolve(false));
grApp = await fixture<GrApp>(html`<gr-app id="app"></gr-app>`);
});
test('models resolve', () => {
// Verify that models resolve on grApp without falling back
// to the ones instantiated by the test-setup.
removeRequestDependencyListener();
assert.ok(resolve(grApp, routerToken)());
});
test('reporting', () => {
assert.isTrue(appStartedStub.calledOnce);
});
test('reporting called before router start', () => {
sinon.assert.callOrder(appStartedStub, routerStartStub);
});
test('_paramsChanged sets search page', () => {
const grAppElement = queryAndAssert<GrAppElement>(grApp, '#app-element');
grAppElement.params = createChangeViewState();
grAppElement.paramsChanged();
assert.notOk(grAppElement.lastSearchPage);
grAppElement.params = createAppElementSearchViewParams();
grAppElement.paramsChanged();
assert.ok(grAppElement.lastSearchPage);
});
test('scroll-padding-top is set when connected', () => {
assert.equal(
document.documentElement.style.getPropertyValue('scroll-padding-top'),
'calc(var(--main-header-height) + var(--change-header-height) + var(--diff-header-height))'
);
});
test('scroll-padding-top is removed when disconnected', () => {
assert.equal(
document.documentElement.style.getPropertyValue('scroll-padding-top'),
'calc(var(--main-header-height) + var(--change-header-height) + var(--diff-header-height))'
);
grApp.remove();
assert.equal(
document.documentElement.style.getPropertyValue('scroll-padding-top'),
''
);
});
test('scroll-padding-top drops to 0px on focus inside sticky header and restores on blur', () => {
const grAppElement = queryAndAssert<GrAppElement>(grApp, '#app-element');
const mainHeader = queryAndAssert(grAppElement, 'gr-main-header');
const searchBar = queryAndAssert(mainHeader, 'gr-smart-search');
const searchAutocomplete = queryAndAssert(
searchBar,
'gr-search-autocomplete'
);
const autocomplete = queryAndAssert(searchAutocomplete, 'gr-autocomplete');
const input = queryAndAssert(autocomplete, '#input');
// 1. Focus inside nested search bar input in sticky mainHeader
input.dispatchEvent(
new FocusEvent('focusin', {bubbles: true, composed: true})
);
assert.equal(
document.documentElement.style.getPropertyValue('scroll-padding-top'),
'0px'
);
// 2. Focus moves to non-sticky content
grAppElement.dispatchEvent(
new FocusEvent('focusin', {bubbles: true, composed: true})
);
assert.equal(
document.documentElement.style.getPropertyValue('scroll-padding-top'),
'calc(var(--main-header-height) + var(--change-header-height) + var(--diff-header-height))'
);
// 3. Re-focus inside sticky header
mainHeader.dispatchEvent(
new FocusEvent('focusin', {bubbles: true, composed: true})
);
assert.equal(
document.documentElement.style.getPropertyValue('scroll-padding-top'),
'0px'
);
// 4. Focus leaves the window (blur, relatedTarget: null)
mainHeader.dispatchEvent(
new FocusEvent('focusout', {
bubbles: true,
composed: true,
relatedTarget: null,
})
);
assert.equal(
document.documentElement.style.getPropertyValue('scroll-padding-top'),
'calc(var(--main-header-height) + var(--change-header-height) + var(--diff-header-height))'
);
});
test('focus transition between sticky elements maintains 0px without intermediate reset', () => {
const grAppElement = queryAndAssert<GrAppElement>(grApp, '#app-element');
const mainHeader = queryAndAssert(grAppElement, 'gr-main-header');
// Create a second sticky element to simulate another sticky header or sibling
const secondSticky = document.createElement('div');
secondSticky.style.position = 'sticky';
secondSticky.style.top = '48px';
grAppElement.shadowRoot!.appendChild(secondSticky);
// Focus first sticky element
mainHeader.dispatchEvent(
new FocusEvent('focusin', {bubbles: true, composed: true})
);
assert.equal(
document.documentElement.style.getPropertyValue('scroll-padding-top'),
'0px'
);
// Focusout from mainHeader transferring to secondSticky
mainHeader.dispatchEvent(
new FocusEvent('focusout', {
bubbles: true,
composed: true,
relatedTarget: secondSticky,
})
);
// Because relatedTarget is non-null, focusout does not prematurely reset
assert.equal(
document.documentElement.style.getPropertyValue('scroll-padding-top'),
'0px'
);
// Focusin on secondSticky
secondSticky.dispatchEvent(
new FocusEvent('focusin', {bubbles: true, composed: true})
);
assert.equal(
document.documentElement.style.getPropertyValue('scroll-padding-top'),
'0px'
);
secondSticky.remove();
});
});