xref: /aosp_15_r20/external/pigweed/pw_web/log-viewer/test/log-view.test.js (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1// Copyright 2024 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15import { assert } from '@open-wc/testing';
16import { MockLogSource } from '../src/custom/mock-log-source';
17import { createLogViewer } from '../src/createLogViewer';
18import { LocalStateStorage, StateService } from '../src/shared/state';
19import { NodeType, Orientation, ViewNode } from '../src/shared/view-node';
20
21function setUpLogViewer(logSources) {
22  const destroyLogViewer = createLogViewer(logSources, document.body);
23  const logViewer = document.querySelector('log-viewer');
24  return { logSources, destroyLogViewer, logViewer };
25}
26
27// Handle benign ResizeObserver error caused by custom log viewer initialization
28// See: https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver#observation_errors
29function handleResizeObserverError() {
30  const e = window.onerror;
31  window.onerror = function (err) {
32    if (
33      err === 'ResizeObserver loop completed with undelivered notifications.'
34    ) {
35      console.warn(
36        'Ignored: ResizeObserver loop completed with undelivered notifications.',
37      );
38      return false;
39    } else {
40      return e(...arguments);
41    }
42  };
43}
44
45describe('log-view', () => {
46  let logSources;
47  let destroyLogViewer;
48  let logViewer;
49  let stateService;
50  let mockColumnData;
51  let mockState;
52
53  async function getLogViews() {
54    const logViewerEl = document.querySelector('log-viewer');
55    await logViewerEl.updateComplete;
56    await new Promise((resolve) => setTimeout(resolve, 100));
57    const logViews = logViewerEl.shadowRoot.querySelectorAll('log-view');
58    return logViews;
59  }
60
61  describe('state', () => {
62    beforeEach(() => {
63      mockColumnData = [
64        {
65          fieldName: 'test',
66          characterLength: 0,
67          manualWidth: null,
68          isVisible: false,
69        },
70        {
71          fieldName: 'foo',
72          characterLength: 0,
73          manualWidth: null,
74          isVisible: true,
75        },
76        {
77          fieldName: 'bar',
78          characterLength: 0,
79          manualWidth: null,
80          isVisible: false,
81        },
82      ];
83
84      mockState = {
85        rootNode: new ViewNode({
86          type: NodeType.Split,
87          orientation: Orientation.Horizontal,
88          children: [
89            new ViewNode({
90              searchText: 'hello',
91              logViewId: 'child-node-1',
92              type: NodeType.View,
93              columnData: mockColumnData,
94            }),
95            new ViewNode({
96              searchText: 'world',
97              logViewId: 'child-node-2',
98              type: NodeType.View,
99              columnData: mockColumnData,
100            }),
101          ],
102        }),
103      };
104
105      stateService = new StateService(new LocalStateStorage());
106      stateService.saveState(mockState);
107      handleResizeObserverError();
108    });
109
110    afterEach(() => {
111      destroyLogViewer();
112    });
113
114    it('should default to single view when state is cleared', async () => {
115      window.localStorage.clear();
116
117      ({ logSources, destroyLogViewer, logViewer } = setUpLogViewer([
118        new MockLogSource(),
119      ]));
120      const logViews = await getLogViews();
121
122      assert.lengthOf(logViews, 1);
123    });
124
125    it('should populate correct number of views from state', async () => {
126      ({ logSources, destroyLogViewer, logViewer } = setUpLogViewer([
127        new MockLogSource(),
128      ]));
129
130      const logViews = await getLogViews();
131      assert.lengthOf(logViews, 2);
132    });
133  });
134
135  describe('sources', () => {
136    before(() => {
137      window.localStorage.clear();
138      ({ logSources, destroyLogViewer, logViewer } = setUpLogViewer([
139        new MockLogSource('Source 1'),
140        new MockLogSource('Source 2'),
141      ]));
142    });
143
144    after(() => {
145      destroyLogViewer();
146      window.localStorage.clear();
147    });
148
149    it('registers a new source upon receiving its first log entry', async () => {
150      const logSource1 = logSources[0];
151
152      logSource1.publishLogEntry({
153        timestamp: new Date(),
154        fields: [{ key: 'message', value: 'Message from Source 1' }],
155      });
156
157      await logViewer.updateComplete;
158      await new Promise((resolve) => setTimeout(resolve, 100));
159
160      const logViews = await getLogViews();
161      const sources = logViews[0]?.sources;
162      const sourceNames = Array.from(sources.values()).map(
163        (source) => source.name,
164      );
165
166      assert.include(
167        sourceNames,
168        'Source 1',
169        'New source should be registered after emitting its first log entry',
170      );
171    });
172
173    it('keeps a record of multiple log sources', async () => {
174      const logSource2 = logSources[1];
175
176      logSource2.publishLogEntry({
177        timestamp: new Date(),
178        fields: [{ key: 'message', value: 'Message from Source 2' }],
179      });
180
181      await logViewer.updateComplete;
182      await new Promise((resolve) => setTimeout(resolve, 100));
183
184      const logViews = await getLogViews();
185      const sources = logViews[0]?.sources;
186      const sourceNames = Array.from(sources.values()).map(
187        (source) => source.name,
188      );
189
190      assert.includeMembers(
191        sourceNames,
192        ['Source 1', 'Source 2'],
193        'Both sources should be present',
194      );
195    });
196  });
197});
198