UNPKG

ohayolibs

Version:

Ohayo is a set of essential modules for ohayojp.

388 lines (349 loc) 11 kB
import { HttpClientTestingModule } from '@angular/common/http/testing'; import { Component, DebugElement, ViewChild } from '@angular/core'; import { ComponentFixture, discardPeriodicTasks, TestBed, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { dispatchFakeEvent, typeInElement } from '@ohayo/testing'; import { OhayoThemeModule } from '@ohayo/theme'; import { deepCopy, deepGet } from '@ohayo/util'; import { NzSafeAny } from 'ng-zorro-antd/core/types'; import { SF_SEQ } from '../src/const'; import { SFButton } from '../src/interface'; import { FormProperty } from '../src/model/form.property'; import { OhayoFormModule } from '../src/module'; import { SFSchema } from '../src/schema'; import { SFUISchema } from '../src/schema/ui'; import { SFComponent } from '../src/sf.component'; export const SCHEMA = { user: { properties: { name: { type: 'string', }, pwd: { type: 'string', }, }, required: ['name', 'pwd'], } as SFSchema, }; let fixture: ComponentFixture<TestFormComponent>; let dl: DebugElement; let context: TestFormComponent; export function builder(options?: { detectChanges?: boolean; template?: string; ingoreAntd?: boolean; imports?: any[]; }): { fixture: ComponentFixture<TestFormComponent>; dl: DebugElement; context: TestFormComponent; page: SFPage; } { options = { detectChanges: true, ...options }; TestBed.configureTestingModule({ imports: [NoopAnimationsModule, OhayoThemeModule.forRoot(), OhayoFormModule.forRoot()].concat(options.imports || []), declarations: [TestFormComponent], }); if (options.template) { TestBed.overrideTemplate(TestFormComponent, options.template); } fixture = TestBed.createComponent(TestFormComponent); dl = fixture.debugElement; context = fixture.componentInstance; spyOn(context, 'formChange'); spyOn(context, 'formValueChange'); spyOn(context, 'formSubmit'); spyOn(context, 'formReset'); spyOn(context, 'formError'); if (options.detectChanges !== false) { fixture.detectChanges(); } const page = new SFPage(context.comp); return { fixture, dl, context, page, }; } export function configureSFTestSuite(): void { beforeEach(() => { TestBed.configureTestingModule({ imports: [NoopAnimationsModule, OhayoThemeModule.forRoot(), OhayoFormModule.forRoot(), HttpClientTestingModule], declarations: [TestFormComponent], }); }); } export class SFPage { constructor(private comp: SFComponent) { } prop(_dl: DebugElement, _context: TestFormComponent, _fixture: ComponentFixture<TestFormComponent>): void { dl = _dl; context = _context; fixture = _fixture; spyOn(context, 'formValueChange'); spyOn(context, 'formChange'); spyOn(context, 'formSubmit'); spyOn(context, 'formReset'); spyOn(context, 'formError'); this.cleanOverlay(); } cleanOverlay(): this { const els = document.querySelectorAll('.cdk-overlay-container'); if (els && els.length > 0) { els.forEach(el => (el.innerHTML = '')); } return this; } getDl(cls: string): DebugElement { return dl.query(By.css(cls)); } getEl(cls: string): HTMLElement { const el = this.getDl(cls); expect(el).not.toBeNull(); return el.nativeElement as HTMLElement; } getWidget<T>(cls: string): T { return this.getDl(cls).componentInstance as T; } private fixPath(path: string): string { return path.startsWith(SF_SEQ) ? path : SF_SEQ + path; } getValue(path: string): any { path = this.fixPath(path); return this.comp.getValue(path); } setValue(path: string, value: any, dc: number = 0): this { path = this.fixPath(path); this.comp.setValue(path, value); if (dc > 0) { this.dc(dc); } return this; } getProperty(path: string): FormProperty { path = this.fixPath(path); return this.comp.getProperty(path)!; } submit(result: boolean = true): this { this.getEl('[data-type="submit"]').click(); if (result) { expect(context.formSubmit).toHaveBeenCalled(); } else { expect(context.formSubmit).not.toHaveBeenCalled(); } return this; } reset(result: boolean = true): this { this.getEl('[data-type="reset"]').click(); if (result) { expect(context.formReset).toHaveBeenCalled(); } else { expect(context.formReset).not.toHaveBeenCalled(); } return this; } isValid(result: boolean = true): this { this.submit(result); expect(this.comp.valid).toBe(result); return this; } add(): this { this.getEl('.sf__array-add button').click(); return this.dc(); } /** 下标从 `1` 开始 */ remove(index: number = 1): this { this.getEl(`.sf__array-container [data-index="${index - 1}"] .sf__array-remove`).click(); return this.dc(); } newSchema(schema: SFSchema, ui?: SFUISchema, formData?: any): this { context.schema = schema; if (typeof ui !== 'undefined') context.ui = ui; if (typeof formData !== 'undefined') context.formData = formData; return this.dc(); } /** 强制指定 `a` 节点 */ chainSchema(schema: SFSchema, overObject: SFSchema): this { context.schema = { ...deepCopy(schema), properties: { a: overObject }, }; return this.dc(); } checkSchema(path: string, propertyName: string, value: any): this { path = this.fixPath(path); const property = this.comp.rootProperty!.searchProperty(path); expect(property != null).toBe(true); const item = property!.schema; const res = deepGet(item, propertyName.split('.'), undefined); expect(res).toBe(value); return this; } checkUI(path: string, propertyName: string, value: any): this { path = this.fixPath(path); const property = this.comp.rootProperty!.searchProperty(path); expect(property != null).toBe(true); const item = property!.ui; const res = deepGet(item, propertyName.split('.'), undefined); expect(res).toBe(value); return this; } checkValue(path: string, value: any, propertyName?: string): this { path = this.fixPath(path); const property = this.comp.rootProperty!.searchProperty(path); expect(property != null).toBe(true); if (typeof propertyName !== 'undefined') { const res = deepGet(property, propertyName.split('.'), undefined); expect(res).toBe(value); } else { expect(property!.value).toBe(value); } return this; } checkCalled(path: string, propertyName: string, result: boolean = true): this { path = this.fixPath(path); const property = this.comp.rootProperty!.searchProperty(path); expect(property != null).toBe(true); const item = property!.ui; const res = deepGet(item, propertyName.split('.'), undefined); if (result) { expect(res).toHaveBeenCalled(); } else { expect(res).not.toHaveBeenCalled(); } return this; } checkElText(cls: string, value: any, viaDocument: boolean = false): this { const node = viaDocument ? document.querySelector(cls) : this.getEl(cls); if (value == null) { expect(node).toBeNull(); } else { expect(node!.textContent!.trim()).toBe(value); } return this; } checkCls(cls: string, value: string): this { const el = this.getEl(cls); expect(el.classList).toContain(value); return this; } checkStyle(cls: string, key: string, value: string): this { const el = this.getEl(cls); expect((el.style as NzSafeAny)[key]).toBe(value); return this; } checkAttr(cls: string, key: string, value: any, required: boolean = true): this { const el = this.getEl(cls); const attr = el.attributes.getNamedItem(key); if (required) expect(attr!.textContent).toBe(value); else expect(attr).toBe(value); return this; } checkCount(cls: string, count: number, viaDocument: boolean = false): this { const len = viaDocument ? document.querySelectorAll(cls).length : dl.queryAll(By.css(cls)).length; expect(len).toBe(count); return this; } checkInput(cls: string, value: any, viaDocument: boolean = false): this { const ipt = (viaDocument ? document.querySelector(cls) : dl.query(By.css(cls)).nativeElement) as HTMLInputElement; expect(ipt.value).toBe(value); return this; } checkError(text: string): this { const el = this.getEl('.ant-form-item-explain'); if (text == null) { expect(el == null).toBe(true); return this; } expect(el.textContent!.trim().includes(text)).toBe(true); return this; } click(cls: string): this { const el = this.getEl(cls); expect(el).not.toBeNull(); el.click(); return this.dc(); } typeChar(value: any, cls: string = 'input'): this { const node = this.getEl(cls) as HTMLInputElement; typeInElement(value, node); tick(); return this.dc(); } typeEvent(eventName: string | Event, cls: string = 'input'): this { const node = document.querySelector(cls) as HTMLInputElement; if (node == null) { expect(true).toBe(false, `won't found '${cls}' class element`); return this; } dispatchFakeEvent(node, eventName); fixture.detectChanges(); return this.time(1000).dc(); } time(time: number = 0): this { tick(time); return this; } dc(time: number = 0): this { fixture.detectChanges(); if (time > 0) { this.time(time); fixture.detectChanges(); } return this; } asyncEnd(time: number = 500): this { this.time(time); discardPeriodicTasks(); return this; } } @Component({ template: ` <sf [layout]="layout" #comp [schema]="schema" [ui]="ui" [formData]="formData" [button]="button" [liveValidate]="liveValidate" [autocomplete]="autocomplete" [firstVisual]="firstVisual" [onlyVisual]="onlyVisual" [disabled]="disabled" [loading]="loading" [noColon]="noColon" [cleanValue]="cleanValue" (formChange)="formChange($event)" (formValueChange)="formValueChange($event)" (formSubmit)="formSubmit($event)" (formReset)="formReset($event)" (formError)="formError($event)" ></sf> `, }) export class TestFormComponent { @ViewChild('comp', { static: true }) comp: SFComponent; mode: 'default' | 'search' | 'edit' = 'default'; layout = 'horizontal'; schema: SFSchema | null = SCHEMA.user; ui: SFUISchema | null = {}; formData: any; button: SFButton | 'none' | null | undefined = {}; liveValidate = true; autocomplete: 'on' | 'off'; firstVisual = true; onlyVisual = false; disabled = false; loading = false; noColon = false; cleanValue = false; formChange(): void { } formValueChange(): void { } formSubmit(): void { } formReset(): void { } formError(): void { } }