mirror of
https://github.com/firstsun-dev/git-files-sync.git
synced 2026-07-22 17:20:30 +00:00
test(ui): add interaction tests for ActionBar, FileListItem, and DiffPanel
Adds comprehensive UI component test coverage with 54 new test cases across three components to close issue #23. Includes JSDOM setup polyfills and tooltip mock utilities for DOM-dependent component testing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
31ac9189cf
commit
a77e0150a1
5 changed files with 467 additions and 0 deletions
|
|
@ -55,6 +55,7 @@ export const App = class {
|
|||
|
||||
export const TFile = class {};
|
||||
export const requestUrl = vi.fn();
|
||||
export const setTooltip = vi.fn();
|
||||
|
||||
vi.mock('obsidian', () => ({
|
||||
Plugin,
|
||||
|
|
@ -67,4 +68,5 @@ vi.mock('obsidian', () => ({
|
|||
App,
|
||||
TFile,
|
||||
requestUrl,
|
||||
setTooltip,
|
||||
}));
|
||||
|
|
|
|||
128
tests/ui/ActionBar.test.ts
Normal file
128
tests/ui/ActionBar.test.ts
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import { describe, it, expect, vi, beforeAll, beforeEach } from 'vitest';
|
||||
import { renderActionBar, type ActionBarProps, type ActionBarCallbacks } from '../../src/ui/components/ActionBar';
|
||||
import { setupObsidianDOM, createContainer } from './setup-dom';
|
||||
|
||||
beforeAll(() => { setupObsidianDOM(); });
|
||||
|
||||
const baseProps = (overrides?: Partial<ActionBarProps>): ActionBarProps => ({
|
||||
hasFiles: true, allSelected: false, indeterminate: false,
|
||||
canPush: 1, canPull: 1, canDelete: 1,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('renderActionBar', () => {
|
||||
let container: HTMLElement;
|
||||
let callbacks: ActionBarCallbacks;
|
||||
|
||||
beforeEach(() => {
|
||||
container = createContainer();
|
||||
callbacks = {
|
||||
onRefresh: vi.fn(),
|
||||
onSelectAll: vi.fn(),
|
||||
onPush: vi.fn(),
|
||||
onPull: vi.fn(),
|
||||
onDelete: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('refresh button', () => {
|
||||
it('always renders when hasFiles is false', () => {
|
||||
renderActionBar(container, baseProps({ hasFiles: false }), callbacks);
|
||||
expect(container.querySelector('.ssv-btn-refresh')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('calls onRefresh when clicked', () => {
|
||||
renderActionBar(container, baseProps({ hasFiles: false }), callbacks);
|
||||
(container.querySelector('.ssv-btn-refresh') as HTMLButtonElement).click();
|
||||
expect(callbacks.onRefresh).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when hasFiles is false', () => {
|
||||
it('does not render push / pull / delete buttons', () => {
|
||||
renderActionBar(container, baseProps({ hasFiles: false }), callbacks);
|
||||
expect(container.querySelector('.ssv-btn-push')).toBeNull();
|
||||
expect(container.querySelector('.ssv-btn-pull')).toBeNull();
|
||||
expect(container.querySelector('.ssv-btn-danger')).toBeNull();
|
||||
});
|
||||
|
||||
it('does not render select-all row', () => {
|
||||
renderActionBar(container, baseProps({ hasFiles: false }), callbacks);
|
||||
expect(container.querySelector('.ssv-select-row')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when hasFiles is true', () => {
|
||||
it('renders push, pull, and delete buttons', () => {
|
||||
renderActionBar(container, baseProps(), callbacks);
|
||||
expect(container.querySelector('.ssv-btn-push')).not.toBeNull();
|
||||
expect(container.querySelector('.ssv-btn-pull')).not.toBeNull();
|
||||
expect(container.querySelector('.ssv-btn-danger')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('renders select-all checkbox', () => {
|
||||
renderActionBar(container, baseProps(), callbacks);
|
||||
expect(container.querySelector('.ssv-select-row input[type="checkbox"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('calls onPush when push button clicked', () => {
|
||||
renderActionBar(container, baseProps(), callbacks);
|
||||
(container.querySelector('.ssv-btn-push') as HTMLButtonElement).click();
|
||||
expect(callbacks.onPush).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('calls onPull when pull button clicked', () => {
|
||||
renderActionBar(container, baseProps(), callbacks);
|
||||
(container.querySelector('.ssv-btn-pull') as HTMLButtonElement).click();
|
||||
expect(callbacks.onPull).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('calls onDelete when delete button clicked', () => {
|
||||
renderActionBar(container, baseProps(), callbacks);
|
||||
(container.querySelector('.ssv-btn-danger') as HTMLButtonElement).click();
|
||||
expect(callbacks.onDelete).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('push button is disabled when canPush is 0', () => {
|
||||
renderActionBar(container, baseProps({ canPush: 0 }), callbacks);
|
||||
expect((container.querySelector('.ssv-btn-push') as HTMLButtonElement).disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('pull button is disabled when canPull is 0', () => {
|
||||
renderActionBar(container, baseProps({ canPull: 0 }), callbacks);
|
||||
expect((container.querySelector('.ssv-btn-pull') as HTMLButtonElement).disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('delete button is disabled when canDelete is 0', () => {
|
||||
renderActionBar(container, baseProps({ canDelete: 0 }), callbacks);
|
||||
expect((container.querySelector('.ssv-btn-danger') as HTMLButtonElement).disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('push button is enabled when canPush > 0', () => {
|
||||
renderActionBar(container, baseProps({ canPush: 3 }), callbacks);
|
||||
expect((container.querySelector('.ssv-btn-push') as HTMLButtonElement).disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('select-all checkbox reflects allSelected prop', () => {
|
||||
renderActionBar(container, baseProps({ allSelected: true }), callbacks);
|
||||
const cb = container.querySelector('.ssv-select-row input') as HTMLInputElement;
|
||||
expect(cb.checked).toBe(true);
|
||||
});
|
||||
|
||||
it('calls onSelectAll(true) when checkbox is checked', () => {
|
||||
renderActionBar(container, baseProps(), callbacks);
|
||||
const cb = container.querySelector('.ssv-select-row input') as HTMLInputElement;
|
||||
cb.checked = true;
|
||||
cb.dispatchEvent(new Event('change'));
|
||||
expect(callbacks.onSelectAll).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('calls onSelectAll(false) when checkbox is unchecked', () => {
|
||||
renderActionBar(container, baseProps({ allSelected: true }), callbacks);
|
||||
const cb = container.querySelector('.ssv-select-row input') as HTMLInputElement;
|
||||
cb.checked = false;
|
||||
cb.dispatchEvent(new Event('change'));
|
||||
expect(callbacks.onSelectAll).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
54
tests/ui/DiffPanel.test.ts
Normal file
54
tests/ui/DiffPanel.test.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
||||
import { renderDiffPanel } from '../../src/ui/components/DiffPanel';
|
||||
import { setupObsidianDOM, createContainer } from './setup-dom';
|
||||
|
||||
beforeAll(() => { setupObsidianDOM(); });
|
||||
|
||||
describe('renderDiffPanel', () => {
|
||||
let container: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
container = createContainer();
|
||||
});
|
||||
|
||||
it('returns the diff element with ssv-diff class', () => {
|
||||
const el = renderDiffPanel(container, '', '');
|
||||
expect(el.classList.contains('ssv-diff')).toBe(true);
|
||||
});
|
||||
|
||||
it('renders Remote and Local column headers', () => {
|
||||
renderDiffPanel(container, 'a', 'b');
|
||||
const headers = container.querySelectorAll('.ssv-diff-hd');
|
||||
expect(headers[0]?.textContent).toBe('Remote');
|
||||
expect(headers[1]?.textContent).toBe('Local');
|
||||
});
|
||||
|
||||
it('renders unchanged lines with unchanged class in both columns', () => {
|
||||
renderDiffPanel(container, 'same', 'same');
|
||||
const cells = container.querySelectorAll('.ssv-diff-cell.unchanged');
|
||||
expect(cells.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it('renders added line in unified view', () => {
|
||||
renderDiffPanel(container, '', 'new line');
|
||||
const added = container.querySelector('.ssv-u-line.added');
|
||||
expect(added?.textContent).toContain('new line');
|
||||
});
|
||||
|
||||
it('renders removed line in unified view', () => {
|
||||
renderDiffPanel(container, 'old line', '');
|
||||
const removed = container.querySelector('.ssv-u-line.removed');
|
||||
expect(removed?.textContent).toContain('old line');
|
||||
});
|
||||
|
||||
it('renders both removed and added lines for a replacement', () => {
|
||||
renderDiffPanel(container, 'before', 'after');
|
||||
expect(container.querySelector('.ssv-u-line.removed')).not.toBeNull();
|
||||
expect(container.querySelector('.ssv-u-line.added')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('renders unchanged lines in unified view for identical content', () => {
|
||||
renderDiffPanel(container, 'context', 'context');
|
||||
expect(container.querySelector('.ssv-u-line.unchanged')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
216
tests/ui/FileListItem.test.ts
Normal file
216
tests/ui/FileListItem.test.ts
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
import { describe, it, expect, vi, beforeAll, beforeEach } from 'vitest';
|
||||
import { renderFileItem, statusMeta, type FileItemCallbacks } from '../../src/ui/components/FileListItem';
|
||||
import type { FileStatus } from '../../src/ui/types';
|
||||
import type { TFile } from 'obsidian';
|
||||
import { setupObsidianDOM, createContainer } from './setup-dom';
|
||||
|
||||
beforeAll(() => { setupObsidianDOM(); });
|
||||
|
||||
const mockFile = {} as TFile;
|
||||
|
||||
function makeFileStatus(status: FileStatus['status'], overrides?: Partial<FileStatus>): FileStatus {
|
||||
return { path: 'docs/test.md', status, ...overrides };
|
||||
}
|
||||
|
||||
describe('statusMeta', () => {
|
||||
it.each([
|
||||
['synced', '✓', 'Synced', 'status-synced'],
|
||||
['modified', '⚠', 'Changed', 'status-modified'],
|
||||
['unsynced', '↑', 'Local only', 'status-unsynced'],
|
||||
['remote-only', '↓', 'Remote', 'status-remote'],
|
||||
['checking', '⟳', 'Checking', 'status-checking'],
|
||||
] as const)('%s: returns correct icon, label, and fileCls', (status, icon, label, fileCls) => {
|
||||
const meta = statusMeta(status);
|
||||
expect(meta.icon).toBe(icon);
|
||||
expect(meta.label).toBe(label);
|
||||
expect(meta.fileCls).toBe(fileCls);
|
||||
});
|
||||
|
||||
it('returns distinct CSS classes for each status', () => {
|
||||
const statuses = ['synced', 'modified', 'unsynced', 'remote-only', 'checking'] as const;
|
||||
const badgeCls = statuses.map(s => statusMeta(s).badgeCls);
|
||||
expect(new Set(badgeCls).size).toBe(statuses.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderFileItem', () => {
|
||||
let container: HTMLElement;
|
||||
let callbacks: FileItemCallbacks;
|
||||
|
||||
beforeEach(() => {
|
||||
container = createContainer();
|
||||
callbacks = {
|
||||
onSelect: vi.fn(),
|
||||
onPush: vi.fn(),
|
||||
onPull: vi.fn(),
|
||||
onDelete: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
it('renders file path', () => {
|
||||
renderFileItem(container, makeFileStatus('synced'), false, callbacks);
|
||||
expect(container.querySelector('.ssv-file-path')?.textContent).toBe('docs/test.md');
|
||||
});
|
||||
|
||||
it('renders status badge with correct label', () => {
|
||||
renderFileItem(container, makeFileStatus('modified'), false, callbacks);
|
||||
expect(container.querySelector('.ssv-status-badge')?.textContent).toBe('Changed');
|
||||
});
|
||||
|
||||
it('checkbox reflects isSelected=true', () => {
|
||||
renderFileItem(container, makeFileStatus('synced'), true, callbacks);
|
||||
expect((container.querySelector('.ssv-file-checkbox') as HTMLInputElement).checked).toBe(true);
|
||||
});
|
||||
|
||||
it('checkbox reflects isSelected=false', () => {
|
||||
renderFileItem(container, makeFileStatus('synced'), false, callbacks);
|
||||
expect((container.querySelector('.ssv-file-checkbox') as HTMLInputElement).checked).toBe(false);
|
||||
});
|
||||
|
||||
it('calls onSelect(path, true) when checkbox checked', () => {
|
||||
renderFileItem(container, makeFileStatus('synced'), false, callbacks);
|
||||
const cb = container.querySelector('.ssv-file-checkbox') as HTMLInputElement;
|
||||
cb.checked = true;
|
||||
cb.dispatchEvent(new Event('change'));
|
||||
expect(callbacks.onSelect).toHaveBeenCalledWith('docs/test.md', true);
|
||||
});
|
||||
|
||||
it('calls onSelect(path, false) when checkbox unchecked', () => {
|
||||
renderFileItem(container, makeFileStatus('synced'), true, callbacks);
|
||||
const cb = container.querySelector('.ssv-file-checkbox') as HTMLInputElement;
|
||||
cb.checked = false;
|
||||
cb.dispatchEvent(new Event('change'));
|
||||
expect(callbacks.onSelect).toHaveBeenCalledWith('docs/test.md', false);
|
||||
});
|
||||
|
||||
describe('synced file', () => {
|
||||
it('renders no action buttons', () => {
|
||||
renderFileItem(container, makeFileStatus('synced'), false, callbacks);
|
||||
expect(container.querySelector('.ssv-file-actions')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('checking file', () => {
|
||||
it('renders no action buttons', () => {
|
||||
renderFileItem(container, makeFileStatus('checking'), false, callbacks);
|
||||
expect(container.querySelector('.ssv-file-actions')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('modified file', () => {
|
||||
it('renders push and pull buttons when file exists', () => {
|
||||
const fs = makeFileStatus('modified', { file: mockFile });
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
expect(container.querySelector('.ssv-action-btn.push')).not.toBeNull();
|
||||
expect(container.querySelector('.ssv-action-btn.pull')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('calls onPush with fileStatus when push clicked', () => {
|
||||
const fs = makeFileStatus('modified', { file: mockFile });
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
(container.querySelector('.ssv-action-btn.push') as HTMLButtonElement).click();
|
||||
expect(callbacks.onPush).toHaveBeenCalledWith(fs);
|
||||
});
|
||||
|
||||
it('calls onPull with fileStatus when pull clicked', () => {
|
||||
const fs = makeFileStatus('modified', { file: mockFile });
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
(container.querySelector('.ssv-action-btn.pull') as HTMLButtonElement).click();
|
||||
expect(callbacks.onPull).toHaveBeenCalledWith(fs);
|
||||
});
|
||||
|
||||
it('renders diff toggle button when diff content is present', () => {
|
||||
const fs = makeFileStatus('modified', { diff: 'some diff', localContent: 'b', remoteContent: 'a' });
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
expect(container.querySelector('.ssv-action-btn.diff')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('does not render diff toggle when no diff', () => {
|
||||
const fs = makeFileStatus('modified', { file: mockFile });
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
expect(container.querySelector('.ssv-action-btn.diff')).toBeNull();
|
||||
});
|
||||
|
||||
it('diff panel is not visible before toggle', () => {
|
||||
const fs = makeFileStatus('modified', { diff: 'd', localContent: 'b', remoteContent: 'a' });
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
expect(container.querySelector('.ssv-diff')?.classList.contains('visible')).toBe(false);
|
||||
});
|
||||
|
||||
it('diff panel becomes visible on first click', () => {
|
||||
const fs = makeFileStatus('modified', { diff: 'd', localContent: 'b', remoteContent: 'a' });
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
(container.querySelector('.ssv-action-btn.diff') as HTMLButtonElement).click();
|
||||
expect(container.querySelector('.ssv-diff')?.classList.contains('visible')).toBe(true);
|
||||
});
|
||||
|
||||
it('diff panel hides on second click', () => {
|
||||
const fs = makeFileStatus('modified', { diff: 'd', localContent: 'b', remoteContent: 'a' });
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
const btn = container.querySelector('.ssv-action-btn.diff') as HTMLButtonElement;
|
||||
btn.click();
|
||||
btn.click();
|
||||
expect(container.querySelector('.ssv-diff')?.classList.contains('visible')).toBe(false);
|
||||
});
|
||||
|
||||
it('diff button label toggles between " Diff" and " Hide"', () => {
|
||||
const fs = makeFileStatus('modified', { diff: 'd', localContent: 'b', remoteContent: 'a' });
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
const btn = container.querySelector('.ssv-action-btn.diff') as HTMLButtonElement;
|
||||
const label = btn.querySelector('.ssv-btn-label') as HTMLElement;
|
||||
expect(label.textContent).toBe(' Diff');
|
||||
btn.click();
|
||||
expect(label.textContent).toBe(' Hide');
|
||||
btn.click();
|
||||
expect(label.textContent).toBe(' Diff');
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsynced file', () => {
|
||||
it('renders push button when file exists', () => {
|
||||
const fs = makeFileStatus('unsynced', { file: mockFile });
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
expect(container.querySelector('.ssv-action-btn.push')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('renders delete button when file exists', () => {
|
||||
const fs = makeFileStatus('unsynced', { file: mockFile });
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
expect(container.querySelector('.ssv-action-btn.danger')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('does not render pull button', () => {
|
||||
const fs = makeFileStatus('unsynced', { file: mockFile });
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
expect(container.querySelector('.ssv-action-btn.pull')).toBeNull();
|
||||
});
|
||||
|
||||
it('calls onDelete with fileStatus when delete clicked', () => {
|
||||
const fs = makeFileStatus('unsynced', { file: mockFile });
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
(container.querySelector('.ssv-action-btn.danger') as HTMLButtonElement).click();
|
||||
expect(callbacks.onDelete).toHaveBeenCalledWith(fs);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remote-only file', () => {
|
||||
it('renders pull button', () => {
|
||||
const fs = makeFileStatus('remote-only');
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
expect(container.querySelector('.ssv-action-btn.pull')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('does not render push button', () => {
|
||||
const fs = makeFileStatus('remote-only');
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
expect(container.querySelector('.ssv-action-btn.push')).toBeNull();
|
||||
});
|
||||
|
||||
it('calls onPull with fileStatus when pull clicked', () => {
|
||||
const fs = makeFileStatus('remote-only');
|
||||
renderFileItem(container, fs, false, callbacks);
|
||||
(container.querySelector('.ssv-action-btn.pull') as HTMLButtonElement).click();
|
||||
expect(callbacks.onPull).toHaveBeenCalledWith(fs);
|
||||
});
|
||||
});
|
||||
});
|
||||
67
tests/ui/setup-dom.ts
Normal file
67
tests/ui/setup-dom.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Sets up a JSDOM environment for DOM-based tests running in the node vitest environment.
|
||||
* Call `setupObsidianDOM()` inside `beforeAll`, and use `createContainer()` for fresh roots.
|
||||
*/
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
let dom: JSDOM;
|
||||
|
||||
export function setupObsidianDOM(): void {
|
||||
dom = new JSDOM('<!DOCTYPE html>');
|
||||
const { window } = dom;
|
||||
|
||||
Object.assign(globalThis, {
|
||||
document: window.document,
|
||||
window: window,
|
||||
HTMLElement: window.HTMLElement,
|
||||
HTMLInputElement: window.HTMLInputElement,
|
||||
Event: window.Event,
|
||||
Text: window.Text,
|
||||
});
|
||||
|
||||
const proto = window.HTMLElement.prototype as HTMLElement;
|
||||
if ('createEl' in proto) return;
|
||||
|
||||
type DomOpts = { cls?: string; text?: string; type?: string };
|
||||
const toOpts = (o?: DomOpts | string): DomOpts => (typeof o === 'string' ? { cls: o } : o ?? {});
|
||||
|
||||
function applyOpts(el: Element, o: DomOpts): void {
|
||||
if (o.cls) el.className = o.cls;
|
||||
if (o.text) el.textContent = o.text;
|
||||
if (o.type && el instanceof window.HTMLInputElement) (el as HTMLInputElement).type = o.type;
|
||||
}
|
||||
|
||||
Object.assign(proto, {
|
||||
createEl<K extends keyof HTMLElementTagNameMap>(tag: K, opts?: DomOpts | string): HTMLElementTagNameMap[K] {
|
||||
const el = window.document.createElement(tag);
|
||||
applyOpts(el, toOpts(opts));
|
||||
(this as HTMLElement).appendChild(el);
|
||||
return el as unknown as HTMLElementTagNameMap[K];
|
||||
},
|
||||
createDiv(opts?: DomOpts | string): HTMLDivElement {
|
||||
const el = window.document.createElement('div');
|
||||
applyOpts(el, toOpts(opts));
|
||||
(this as HTMLElement).appendChild(el);
|
||||
return el as unknown as HTMLDivElement;
|
||||
},
|
||||
createSpan(opts?: DomOpts | string): HTMLSpanElement {
|
||||
const el = window.document.createElement('span');
|
||||
applyOpts(el, toOpts(opts));
|
||||
(this as HTMLElement).appendChild(el);
|
||||
return el as unknown as HTMLSpanElement;
|
||||
},
|
||||
hasClass(cls: string): boolean {
|
||||
return (this as HTMLElement).classList.contains(cls);
|
||||
},
|
||||
toggleClass(cls: string, value: boolean): void {
|
||||
(this as HTMLElement).classList.toggle(cls, value);
|
||||
},
|
||||
setText(text: string): void {
|
||||
(this as HTMLElement).textContent = text;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function createContainer(): HTMLElement {
|
||||
return dom.window.document.createElement('div') as unknown as HTMLElement;
|
||||
}
|
||||
Loading…
Reference in a new issue