| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import type * as SDK from '../../../core/sdk/sdk.js'; |
| import * as Protocol from '../../../generated/protocol.js'; |
| import { |
| getCleanTextContentFromElements, |
| getElementWithinComponent, |
| renderElementIntoDOM, |
| } from '../../../testing/DOMHelpers.js'; |
| import {setupLocaleHooks} from '../../../testing/LocaleHelpers.js'; |
| import * as RenderCoordinator from '../../../ui/components/render_coordinator/render_coordinator.js'; |
| import * as ReportView from '../../../ui/components/report_view/report_view.js'; |
| import * as UI from '../../../ui/legacy/legacy.js'; |
| |
| import * as ApplicationComponents from './components.js'; |
| |
| async function makeView( |
| storageKeyOrBucketInfo: string|Protocol.Storage.StorageBucketInfo, |
| storageBucketsModel?: SDK.StorageBucketsModel.StorageBucketsModel) { |
| const component = new ApplicationComponents.StorageMetadataView.StorageMetadataView(); |
| renderElementIntoDOM(component); |
| if (storageBucketsModel) { |
| component.enableStorageBucketControls(storageBucketsModel); |
| } |
| if (typeof storageKeyOrBucketInfo === 'string') { |
| component.setStorageKey(storageKeyOrBucketInfo); |
| } else { |
| component.setStorageBucket(storageKeyOrBucketInfo); |
| } |
| await RenderCoordinator.done(); |
| return component; |
| } |
| |
| describe('SharedStorageMetadataView', () => { |
| setupLocaleHooks(); |
| it('renders with an origin only', async () => { |
| const component = await makeView('https://example.com/'); |
| |
| const report = getElementWithinComponent(component, 'devtools-report', ReportView.ReportView.Report); |
| const {textContent} = report.shadowRoot!.querySelector('.report-title')!; |
| assert.strictEqual(textContent, 'https://example.com'); |
| |
| assert.isNotNull(component.shadowRoot); |
| |
| const keys = getCleanTextContentFromElements(component.shadowRoot, 'devtools-report-key'); |
| assert.deepEqual(keys, []); |
| |
| const values = getCleanTextContentFromElements(component.shadowRoot, 'devtools-report-value'); |
| assert.deepEqual(values, []); |
| }); |
| |
| it('renders with an top-level site', async () => { |
| const component = await makeView('https://example.com/^0https://test.example'); |
| |
| const report = getElementWithinComponent(component, 'devtools-report', ReportView.ReportView.Report); |
| const {textContent} = report.shadowRoot!.querySelector('.report-title')!; |
| assert.strictEqual(textContent, 'https://example.com'); |
| |
| assert.isNotNull(component.shadowRoot); |
| |
| const keys = getCleanTextContentFromElements(component.shadowRoot, 'devtools-report-key'); |
| assert.deepEqual(keys, ['Frame origin', 'Top-level site', 'Is third-party']); |
| |
| const values = getCleanTextContentFromElements(component.shadowRoot, 'devtools-report-value'); |
| assert.deepEqual( |
| values, |
| ['https://example.com', 'https://test.example', 'Yes, because the origin is outside of the top-level site']); |
| }); |
| |
| it('renders with an opaque top-level site', async () => { |
| const component = await makeView('https://example.com/^43735928559^5110521^6'); |
| |
| const report = getElementWithinComponent(component, 'devtools-report', ReportView.ReportView.Report); |
| const {textContent} = report.shadowRoot!.querySelector('.report-title')!; |
| assert.strictEqual(textContent, 'https://example.com'); |
| |
| assert.isNotNull(component.shadowRoot); |
| |
| const keys = getCleanTextContentFromElements(component.shadowRoot, 'devtools-report-key'); |
| assert.deepEqual(keys, ['Top-level site', 'Is third-party', 'Is opaque']); |
| |
| const values = getCleanTextContentFromElements(component.shadowRoot, 'devtools-report-value'); |
| assert.deepEqual(values, [ |
| '(opaque)', |
| 'Yes, because the top-level site is opaque', |
| 'Yes, because the top-level site is opaque', |
| ]); |
| }); |
| |
| it('renders with an opaque key', async () => { |
| const component = await makeView('https://example.com/^13735928559^2110521'); |
| |
| const report = getElementWithinComponent(component, 'devtools-report', ReportView.ReportView.Report); |
| const {textContent} = report.shadowRoot!.querySelector('.report-title')!; |
| assert.strictEqual(textContent, 'https://example.com'); |
| |
| assert.isNotNull(component.shadowRoot); |
| |
| const keys = getCleanTextContentFromElements(component.shadowRoot, 'devtools-report-key'); |
| assert.deepEqual(keys, ['Is third-party', 'Is opaque']); |
| |
| const values = getCleanTextContentFromElements(component.shadowRoot, 'devtools-report-value'); |
| assert.deepEqual(values, ['Yes, because the storage key is opaque', 'Yes']); |
| }); |
| |
| it('renders with a cross-site ancestor chain', async () => { |
| const component = await makeView('https://example.com/^31'); |
| |
| const report = getElementWithinComponent(component, 'devtools-report', ReportView.ReportView.Report); |
| const {textContent} = report.shadowRoot!.querySelector('.report-title')!; |
| assert.strictEqual(textContent, 'https://example.com'); |
| |
| assert.isNotNull(component.shadowRoot); |
| |
| const keys = getCleanTextContentFromElements(component.shadowRoot, 'devtools-report-key'); |
| assert.deepEqual(keys, ['Is third-party']); |
| |
| const values = getCleanTextContentFromElements(component.shadowRoot, 'devtools-report-value'); |
| assert.deepEqual(values, ['Yes, because the ancestry chain contains a third-party origin']); |
| }); |
| |
| it('renders with a bucket', async () => { |
| const component = await makeView({ |
| bucket: {storageKey: 'https://example.com/^31', name: 'My Bucket'}, |
| id: 'BUCKET_ID', |
| persistent: true, |
| durability: Protocol.Storage.StorageBucketsDurability.Relaxed, |
| quota: 4096, |
| expiration: 42, |
| }); |
| |
| const report = getElementWithinComponent(component, 'devtools-report', ReportView.ReportView.Report); |
| const {textContent} = report.shadowRoot!.querySelector('.report-title')!; |
| assert.strictEqual(textContent, 'https://example.com'); |
| |
| assert.isNotNull(component.shadowRoot); |
| |
| const keys = getCleanTextContentFromElements(component.shadowRoot, 'devtools-report-key'); |
| assert.deepEqual(keys, ['Is third-party', 'Bucket name', 'Is persistent', 'Durability', 'Quota', 'Expiration']); |
| |
| const values = getCleanTextContentFromElements(component.shadowRoot, 'devtools-report-value'); |
| assert.deepEqual(values, [ |
| 'Yes, because the ancestry chain contains a third-party origin', |
| 'My Bucket', |
| 'Yes', |
| 'relaxed', |
| '4.1 kB', |
| (new Date(42000)).toLocaleString(), |
| ]); |
| }); |
| |
| it('renders with an emtpy string title', async () => { |
| const component = await makeView(''); |
| |
| const report = getElementWithinComponent(component, 'devtools-report', ReportView.ReportView.Report); |
| assert.isNull(report.shadowRoot!.querySelector('.report-title')); |
| }); |
| |
| it('renders bucket controls', async () => { |
| const storageBucketsModel = { |
| deleteBucket: sinon.spy(), |
| target: () => ({ |
| model: () => ({ |
| getBucketByName: () => null, |
| }), |
| }), |
| }; |
| |
| const storageBucket = { |
| storageKey: 'https://example.com/^31', |
| name: 'My Bucket', |
| }; |
| |
| const component = await makeView( |
| { |
| bucket: storageBucket, |
| id: 'BUCKET_ID', |
| persistent: true, |
| durability: Protocol.Storage.StorageBucketsDurability.Relaxed, |
| quota: 4096, |
| expiration: 42, |
| }, |
| storageBucketsModel as unknown as SDK.StorageBucketsModel.StorageBucketsModel); |
| |
| const buttons = component.shadowRoot!.querySelectorAll('devtools-button'); |
| |
| assert.lengthOf(buttons, 1); |
| |
| const [deleteButton] = buttons; |
| assert.instanceOf(deleteButton, HTMLElement); |
| assert.strictEqual(deleteButton.textContent!.trim(), 'Delete bucket'); |
| |
| const showDialog = sinon.stub(UI.UIUtils.ConfirmDialog, 'show').resolves(true); |
| deleteButton.click(); |
| sinon.assert.calledOnce(showDialog); |
| |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| sinon.assert.calledOnceWithExactly(storageBucketsModel.deleteBucket, storageBucket); |
| }); |
| }); |