| // Copyright 2022 The Fuchsia 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 {CURRENT_VERSION, MemoryStore, State} from '../src/state'; |
| import {logDataForTest} from './util'; |
| import { ExternalActionRequestEvent, LoggingView, PAUSING_LOG_STREAMING_LOG, RESUMING_LOG_STREAMING_LOG } from '../components/view'; |
| import * as constant from '../src/constants'; |
| import chai from 'chai'; |
| import { expect } from 'chai'; |
| import chaiDom from 'chai-dom'; // not esm |
| import { LogControl } from '../components/log_control'; |
| import { Filter } from '../src/filter'; |
| import { LogViewActions } from '../components/log_view_actions'; |
| import { ffxLogToLogRowData } from '../src/ffxLogToLogRow'; |
| import {LogList, WRAP_LOG_TEXT_ATTR} from '../components/log_list'; |
| import {LitElement} from 'lit'; |
| import {LogViewOptions} from '../src/fields'; |
| |
| const logOptions: LogViewOptions = { |
| columnFormatter: (_fieldName, text) => text, |
| showControls: false |
| }; |
| |
| before(function () { |
| chai.should(); |
| chai.use(chaiDom); |
| }); |
| |
| describe('LoggingView', () => { |
| const filterText = 'moniker:core/foo'; |
| let root: HTMLElement; |
| let vscode: MemoryStore; |
| let state: State; |
| |
| beforeEach(() => { |
| root = document.createElement('div'); |
| document.body.appendChild(root); |
| vscode = new MemoryStore(); |
| state = new State(vscode); |
| }); |
| |
| afterEach(() => { |
| root.remove(); |
| }); |
| |
| describe('#constructor', () => { |
| it('crates view with a log list container and a log action container', () => { |
| new LoggingView(state, root); |
| root.children.length.should.equal(2); |
| root.children[0].id.should.equal('log-action-container'); |
| root.children[1].id.should.equal('log-list-container'); |
| }); |
| |
| it('creates log action container with a log control and log view actions', () => { |
| new LoggingView(state, root); |
| const container = root.children[0]; |
| container.children.length.should.equal(2); |
| container.children[0].tagName.toLowerCase().should.equal('log-control'); |
| container.children[1].tagName.toLowerCase().should.equal('log-view-actions'); |
| }); |
| |
| it('initializes log control with the current state filters', async () => { |
| vscode.setState({ |
| version: CURRENT_VERSION, |
| filter: filterText, |
| fields: constant.LOGS_HEADERS, |
| wrappingLogs: true |
| }); |
| new LoggingView(new State(vscode), root); |
| let logControl = root.getElementsByTagName('log-control')[0] as LogControl; |
| await logControl.updateComplete; |
| logControl.value.should.equal(filterText); |
| }); |
| |
| it('initializes log actions view with the current state wrapping logs', async () => { |
| vscode.setState({ |
| version: CURRENT_VERSION, |
| filter: filterText, |
| fields: constant.LOGS_HEADERS, |
| wrappingLogs: true |
| }); |
| new LoggingView(new State(vscode), root); |
| let logViewActions = root.getElementsByTagName('log-view-actions')[0] as LogViewActions; |
| await logViewActions.updateComplete; |
| logViewActions.wrappingLogs.should.be.true; |
| }); |
| |
| it('crates log list container with log list', () => { |
| new LoggingView(state, root); |
| const container = root.children[1]; |
| container.children.length.should.equal(1); |
| container.children[0].nodeName.should.equal('LOG-VIEW'); |
| }); |
| }); |
| |
| describe('log control integration', () => { |
| it('updates the state on log control filter changes', async () => { |
| new LoggingView(state, root); |
| let logControl = root.getElementsByTagName('log-control')[0] as LogControl; |
| await logControl.updateComplete; |
| const searchBox = logControl.shadowRoot!.querySelector('#search') as HTMLInputElement; |
| searchBox.value = filterText; |
| searchBox.dispatchEvent(new KeyboardEvent('keypress', { 'key': 'Enter' })); |
| await logControl.updateComplete; |
| state.currentFilter.should.deep.equal(new Filter({ |
| category: 'moniker', |
| operator: 'contains', |
| subCategory: undefined, |
| value: 'core/foo', |
| })); |
| state.currentFilterText.should.deep.equal(filterText); |
| }); |
| |
| it('log control and filter field can be disabled', async () => { |
| new LoggingView(state, root, logOptions); |
| let logView = root.getElementsByTagName('log-view')[0] as LogList; |
| await logView.updateComplete; |
| root.getElementsByTagName('log-control').length.should.equal(0); |
| }); |
| |
| it('announces the number of filtered results', async () => { |
| const view = new LoggingView(state, root); |
| const logList = root.querySelector('log-view') as LitElement; |
| view.addLog(ffxLogToLogRowData(logDataForTest('core/foo'))!); |
| view.addLog(ffxLogToLogRowData(logDataForTest('core/foo'))!); |
| await logList.updateComplete; |
| logList.shadowRoot!.children.length.should.equal(3); |
| let logControl = root.getElementsByTagName('log-control')[0] as LogControl; |
| await logControl.updateComplete; |
| const searchBox = logControl.shadowRoot!.querySelector('#search') as HTMLInputElement; |
| const filterText = 'moniker:core/foo'; |
| searchBox.value = filterText; |
| searchBox.dispatchEvent(new KeyboardEvent('keypress', { 'key': 'Enter' })); |
| await logControl.updateComplete; |
| const filterResults = logControl |
| .shadowRoot!.querySelector('#filter-results') as HTMLParagraphElement; |
| filterResults.innerText.should.match(/(2 results)/); |
| }); |
| }); |
| |
| describe('log actions view integration', () => { |
| it('clears the log list when the clear button is clicked', async () => { |
| const view = new LoggingView(state, root); |
| let gotResetEvent: ExternalActionRequestEvent | null = null; |
| view.addEventListener(LoggingView.externalActionRequestEvent, (e) => { |
| gotResetEvent = (e as CustomEvent).detail; |
| }); |
| const logList = root.querySelector('log-view') as LitElement; |
| view.addLog(ffxLogToLogRowData(logDataForTest('core/foo'))!); |
| await logList.updateComplete; |
| logList.shadowRoot!.children.length.should.equal(2); |
| |
| let logViewActions = root.getElementsByTagName('log-view-actions')[0] as LogViewActions; |
| let clearButton = logViewActions.shadowRoot!.querySelector('#clear') as HTMLDivElement; |
| clearButton.click(); |
| await logViewActions.updateComplete; |
| expect(gotResetEvent).to.exist.and.have.property('type').equal('clear-logs'); |
| logList.shadowRoot!.children.length.should.equal(1); |
| }); |
| |
| it('updates the state and log list when the wrap logs button is clicked', async () => { |
| const view = new LoggingView(state, root); |
| const logList = root.querySelector('log-view') as LitElement; |
| view.addLog(ffxLogToLogRowData(logDataForTest('core/foo'))!); |
| await logList.updateComplete; |
| logList.shadowRoot!.children.length.should.equal(2); |
| |
| let logViewActions = root.getElementsByTagName('log-view-actions')[0] as LogViewActions; |
| await logViewActions.updateComplete; |
| let wrapLogsButton = logViewActions.shadowRoot!.querySelector('#wrap-logs') as HTMLDivElement; |
| |
| // Clicking the button for the first should update the other elements to true. |
| // Since we started on true (default state value). |
| wrapLogsButton.click(); |
| await logViewActions.updateComplete; |
| |
| state.shouldWrapLogs.should.be.false; |
| logList.hasAttribute(WRAP_LOG_TEXT_ATTR).should.be.false; |
| |
| // Clicking the button again should update the other elements to false. |
| wrapLogsButton.click(); |
| await logViewActions.updateComplete; |
| |
| state.shouldWrapLogs.should.be.true; |
| logList.hasAttribute(WRAP_LOG_TEXT_ATTR).should.be.true; |
| }); |
| |
| it('renders a synthetic log about pause/play and emits events', async () => { |
| const view = new LoggingView(state, root); |
| const logList = root.querySelector('log-view') as LitElement; |
| |
| let logViewActions = root.getElementsByTagName('log-view-actions')[0] as LogViewActions; |
| await logViewActions.updateComplete; |
| let playPauseButton = |
| logViewActions.shadowRoot!.querySelector('#play-pause') as HTMLDivElement; |
| |
| let promise = new Promise<ExternalActionRequestEvent>((resolve) => { |
| view.addEventListener(LoggingView.externalActionRequestEvent, (e) => { |
| const event = e as CustomEvent; |
| resolve(event.detail); |
| }); |
| }); |
| |
| playPauseButton.click(); |
| let event = await promise; |
| await logList.updateComplete; |
| |
| event.should.deep.equal({ type: 'pause-log-streaming' }); |
| let logLine = logList.shadowRoot!.children[1] as HTMLElement; |
| (logLine.querySelector('#message')! as HTMLTableCellElement) |
| .innerText.should.equal(PAUSING_LOG_STREAMING_LOG); |
| (logLine.querySelector('#moniker')! as HTMLTableCellElement) |
| .innerText.should.equal(constant.VSCODE_SYNTHETIC_MONIKER); |
| |
| // Clicking the button again should show the "Play" state. |
| promise = new Promise<ExternalActionRequestEvent>((resolve) => { |
| view.addEventListener(LoggingView.externalActionRequestEvent, (e) => { |
| const event = e as CustomEvent; |
| resolve(event.detail); |
| }); |
| }); |
| |
| playPauseButton.click(); |
| event = await promise; |
| await logViewActions.updateComplete; |
| |
| event.should.deep.equal({ type: 'resume-log-streaming' }); |
| logLine = logList.shadowRoot!.children[2] as HTMLElement; |
| (logLine.querySelector('#message')! as HTMLTableCellElement) |
| .innerText.should.equal(RESUMING_LOG_STREAMING_LOG); |
| (logLine.querySelector('#moniker')! as HTMLTableCellElement) |
| .innerText.should.equal(constant.VSCODE_SYNTHETIC_MONIKER); |
| }); |
| }); |
| }); |