UNPKG

ohayolibs

Version:

Ohayo is a set of essential modules for ohayojp.

820 lines (775 loc) 26.3 kB
import { Component, DebugElement } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ACLService, OhayoACLModule } from '@ohayo/acl'; import { createTestContext } from '@ohayo/testing'; import { OhayoI18NService, OhayoThemeModule, OHAYO_I18N_TOKEN, OhayoLocaleService, en_US } from '@ohayo/theme'; import { deepCopy } from '@ohayo/util'; import { NzSafeAny } from 'ng-zorro-antd/core/types'; import { NzIconService } from 'ng-zorro-antd/icon'; import { of } from 'rxjs'; import { FormPropertyFactory } from '../src/model/form.property.factory'; import { OhayoFormModule } from '../src/module'; import { SFSchema } from '../src/schema/index'; import { WidgetRegistry } from '../src/widget.factory'; import { SCHEMA, SFPage, TestFormComponent } from './base.spec'; describe('form: component', () => { let fixture: ComponentFixture<TestFormComponent>; let dl: DebugElement; let context: TestFormComponent; let page: SFPage; function genModule(options: { acl?: boolean; i18n?: boolean } = {}): void { options = { acl: false, i18n: false, ...options }; const imports = [NoopAnimationsModule, OhayoFormModule.forRoot(), OhayoThemeModule.forRoot()]; if (options.i18n) { imports.push(OhayoThemeModule.forRoot()); } if (options.acl) { imports.push(OhayoACLModule.forRoot()); } TestBed.configureTestingModule({ imports, declarations: [TestFormComponent, TestModeComponent], }); } function createComp(): void { fixture.detectChanges(); page = new SFPage(context.comp); page.prop(dl, context, fixture); } describe('', () => { beforeEach(() => { genModule(); ({ fixture, dl, context } = createTestContext(TestFormComponent)); createComp(); }); describe('[default]', () => { it('should throw error when parent is not object or array', () => { expect(() => { // tslint:disable-next-line: no-string-literal const factory = context.comp['formPropertyFactory'] as FormPropertyFactory; factory.createProperty({}, {}, {}, { type: 'invalid', path: 'a' } as any, 'a'); }).toThrowError(`Instanciation of a FormProperty with an unknown parent type: invalid`); }); it('should throw error when type is invalid', () => { expect(() => { context.schema = { properties: { a: { type: 'aa' as any, }, }, }; fixture.detectChanges(); }).toThrowError(`Undefined type aa`); }); it('should throw error when is invalid schema', () => { expect(() => { context.schema = null; fixture.detectChanges(); }).toThrowError('Invalid Schema'); }); it(`Don't support string with root ui property`, () => { expect(() => { context.schema = { ui: 'string', properties: {}, }; fixture.detectChanges(); }).toThrowError(`Don't support string with root ui property`); }); it('should be used default widget when is invalid schema type', () => { spyOn(console, 'warn'); expect(console.warn).not.toHaveBeenCalled(); context.schema = { type: 'string', properties: {}, ui: { widget: 'asdf', }, }; fixture.detectChanges(); expect(console.warn).toHaveBeenCalled(); }); it('should be console debug informations', () => { spyOn(console, 'warn'); expect(console.warn).not.toHaveBeenCalled(); context.schema = { properties: { name: { type: 'string' }, age: { type: 'number' }, }, required: ['name', 'age'], ui: { debug: true, }, }; fixture.detectChanges(); page.setValue('/name', 'a'); expect(console.warn).toHaveBeenCalled(); }); it('should be console debug informations when ajv throw error', () => { spyOn(console, 'warn'); expect(console.warn).not.toHaveBeenCalled(); context.schema = { properties: { time: { type: 'string', ui: { widget: 'date', mode: 'range' }, title: 'Date', format: 'yyyy-MM-dd HH:mm:ss', }, }, ui: { debug: true, }, }; fixture.detectChanges(); expect(console.warn).toHaveBeenCalled(); }); it('should be ingore required when element is hidden', () => { const s: SFSchema = { properties: { name: { type: 'string', ui: { hidden: true }, }, }, required: ['name'], }; page.newSchema(s); expect(context.comp._schema.required!.indexOf('name') === -1).toBe(true); }); it('should be ingore trigger formChange event when call refreshSchema method', () => { expect(context.formChange).not.toHaveBeenCalled(); page.newSchema({ properties: { name: { type: 'string' } } }); expect(context.formChange).not.toHaveBeenCalled(); }); it('should be hava values when call refreshSchema method after', () => { page.newSchema({ properties: { name: { type: 'string', default: 'a' } } }); expect(context.formChange).not.toHaveBeenCalled(); expect(context.comp.value.name).toBe('a'); }); }); describe('[button]', () => { it('should be has a primary button when default value', () => { page.checkCount('.sf-btns', 1).checkCount('.ant-btn-primary', 1); }); it('should be null', () => { context.button = null; fixture.detectChanges(); page.checkCount('.sf-btns', 1).checkCount('button', 0); }); it('should be undefined', () => { context.button = undefined; fixture.detectChanges(); page.checkCount('.sf-btns', 1).checkCount('button', 0); }); it('should be none', () => { context.button = 'none'; fixture.detectChanges(); page.checkCount('.sf-btns', 0); }); it('should be icon', () => { context.button = { submit_icon: { type: 'search', }, reset_icon: { type: 'file', }, }; fixture.detectChanges(); page.checkCount('[type="submit"] .anticon', 1); page.checkCount('[type="button"] .anticon', 1); }); describe('when layout is horizontal', () => { it('should be has a fix 100px width', () => { page .newSchema({ properties: { name: { type: 'string', ui: { spanLabelFixed: 100, }, }, }, }) .checkStyle('.sf-btns .ant-form-item-control', 'margin-left', '100px'); }); it('should be specified grid', () => { const span = 11; context.button = { render: { grid: { span }, }, }; fixture.detectChanges(); page.checkCls('.sf-btns .ant-form-item-control', `ant-col-${span}`); }); it('should be fixed label', () => { const spanLabelFixed = 56; context.button = { render: { spanLabelFixed, }, }; fixture.detectChanges(); page.checkStyle('.sf-btns .ant-form-item-control', 'margin-left', `${spanLabelFixed}px`); }); }); describe('#size', () => { it('with small', () => { context.button = { render: { size: 'small' } }; fixture.detectChanges(); page.checkCount('.ant-btn-sm', 2); }); it('with large', () => { context.button = { render: { size: 'large' } }; fixture.detectChanges(); page.checkCount('.ant-btn-lg', 2); }); }); it('should be update button text when i18n changed', () => { page.checkElText('.ant-btn-primary', '提交'); const i18n = TestBed.inject<OhayoLocaleService>(OhayoLocaleService) as OhayoLocaleService; i18n.setLocale(en_US); fixture.detectChanges(); page.checkElText('.ant-btn-primary', 'Submit'); }); }); describe('properites', () => { describe('#validate', () => { it('should be validate when submitted and not liveValidate', () => { page.submit(false); expect((page.getEl('.ant-btn-primary') as HTMLButtonElement).disabled).toBe(true); context.liveValidate = false; fixture.detectChanges(); page.submit(false).setValue('/name', 'cipchk').setValue('/pwd', '1111').submit(true); }); }); describe('#submit', () => { it('should be submit when is valid', () => { page.setValue('/name', 'cipchk').setValue('/pwd', '1111').isValid(); }); it('should not be submit when is invalid', () => { page.setValue('/name', 'cipchk').isValid(false); }); }); describe('#reset', () => { it('should be set default value', () => { const schema = deepCopy(SCHEMA.user) as SFSchema; schema.properties!.name.default = 'cipchk'; page.newSchema(schema).reset().checkValue('/name', 'cipchk'); }); }); describe('#layout', () => { ['horizontal', 'vertical', 'inline'].forEach(type => { it(`with ${type}`, () => { context.layout = type; fixture.detectChanges(); page.checkCls('form', `ant-form-${type}`); }); }); describe(`when with horizontal`, () => { it('shoule be fixed label width', () => { page .newSchema({ properties: { name: { type: 'string', ui: { spanLabelFixed: 100, }, }, }, }) .checkStyle('.ant-form-item-label', 'flex', '0 0 100px') .checkStyle('.ant-form-item-control', 'maxWidth', 'calc(100% - 100px)'); }); it('should inherit parent node', () => { page .newSchema({ properties: { address: { type: 'object', ui: { spanLabelFixed: 98 }, properties: { city: { type: 'string' }, }, }, }, }) .checkStyle('.ant-form-item-label', 'flex', '0 0 98px') .checkStyle('.ant-form-item-control', 'maxWidth', 'calc(100% - 98px)'); }); }); }); describe('#autocomplete', () => { [null, 'on', 'off'].forEach((type: any) => { it(`with [${type}]`, () => { context.autocomplete = type; fixture.detectChanges(); page.checkAttr('form', 'autocomplete', type, !!type); }); }); }); describe('#firstVisual', () => { it('with false', () => { context.firstVisual = false; fixture.detectChanges(); page.checkCount('.ant-form-item-explain', 0); }); it('with true', () => { context.firstVisual = true; fixture.detectChanges(); page.checkCount('.ant-form-item-explain', 2); }); }); describe('#onlyVisual', () => { it('with false', () => { context.onlyVisual = false; fixture.detectChanges(); page.checkCount('.sf__no-error', 0); page.checkCount('.ant-form-item-explain', 2); }); it('with true', () => { context.onlyVisual = true; fixture.detectChanges(); page.checkCount('.sf__no-error', 1); page.checkCount('.ant-form-item-explain', 0); }); }); it('#disabled', () => { const CLS: { [key: string]: string | Array<NzSafeAny> } = { input: '.ant-input[disabled]', number: '.ant-input-number-disabled', switch: '.ant-switch-disabled', }; page.newSchema({ properties: { ipt: { type: 'string' }, number: { type: 'number' }, switch: { type: 'boolean' }, select: { type: 'string', enum: ['A'] }, }, }); context.disabled = false; fixture.detectChanges(); Object.keys(CLS).forEach(key => { if (Array.isArray(CLS[key])) { page.checkCount(CLS[key][0][0], CLS[key][0][1]); } else { page.checkCount(CLS[key] as string, 0); } }); context.disabled = true; fixture.detectChanges(); Object.keys(CLS).forEach(key => { if (Array.isArray(CLS[key])) { page.checkCount(CLS[key][1][0], CLS[key][1][1]); } else { page.checkCount(CLS[key] as string, 1); } }); }); it('#loading', () => { context.loading = false; fixture.detectChanges(); const CLS = { loading: '[data-type="submit"].ant-btn-loading', disabled: '[data-type="reset"][disabled]', }; page.checkCount(CLS.loading, 0); page.checkCount(CLS.disabled, 0); context.loading = true; fixture.detectChanges(); page.checkCount(CLS.loading, 1); page.checkCount(CLS.disabled, 1); }); it('#noColon', () => { context.noColon = true; fixture.detectChanges(); const CLS = `.sf__no-colon`; page.checkCount(CLS, 1); context.noColon = false; fixture.detectChanges(); page.checkCount(CLS, 0); }); describe('#cleanValue', () => { it('with true', () => { context.cleanValue = true; fixture.detectChanges(); page.newSchema( { properties: { name: { type: 'string' }, arr: { type: 'array', items: { type: 'object', properties: { x: { type: 'string' } } } }, }, }, {}, { name: 'a', age: 10, arr: [{ x: 1, y: 2 }] }, ); expect(context.comp.value.age == null).toBe(true); expect(context.comp.value.arr[0].y == null).toBe(true); }); it('with false', () => { context.cleanValue = false; fixture.detectChanges(); page.newSchema( { properties: { name: { type: 'string' }, arr: { type: 'array', items: { type: 'object', properties: { x: { type: 'string' } } } }, }, }, {}, { name: 'a', age: 10, arr: [{ x: 1, y: 2 }] }, ); expect(context.comp.value.age).toBe(10); expect(context.comp.value.arr[0].y).toBe(2); }); }); it('#formChange', () => { page.setValue('/name', 'cipchk'); expect(context.formChange).toHaveBeenCalled(); }); it('#formValueChange', () => { page.setValue('/name', 'cipchk'); expect(context.formValueChange).toHaveBeenCalled(); }); it('#formSubmit', () => { page.setValue('/name', 'cipchk').setValue('/pwd', 'asdf').submit(); expect(context.formSubmit).toHaveBeenCalled(); }); it('#formReset', () => { page.setValue('/name', 'cipchk').setValue('/pwd', 'asdf').reset(); expect(context.formReset).toHaveBeenCalled(); }); it('#formError', () => { page.setValue('/name', 'cipchk').setValue('/name', ''); expect(context.formError).toHaveBeenCalled(); }); }); describe('[widgets]', () => { it('#size', () => { page .newSchema({ properties: { name: { type: 'string', ui: { size: 'large' } } }, }) .checkCls('input', 'ant-input-lg'); }); it('#disabled', fakeAsync(() => { const el = page.newSchema({ properties: { name: { type: 'string', readOnly: true } } }).getEl('input') as HTMLInputElement; tick(); expect(el.disabled).toBe(true); expect(el.classList).toContain('ant-input-disabled'); })); it('should be custom class', () => { page .newSchema({ properties: { name: { type: 'string', ui: { class: 'test-cls' } } }, }) .checkCls('sf-string', 'test-cls'); }); it('should get all registered widgets', () => { const wr = TestBed.inject(WidgetRegistry) as WidgetRegistry; expect(Object.keys(wr.widgets).length).toBeGreaterThan(0); }); }); describe('public methods', () => { it('#getProperty', () => { expect(context.comp.getProperty('/name')).not.toBeNull(); }); it('#getValue', () => { const name = 'asdf'; page.newSchema({ properties: { name: { type: 'string' } } }, null!, { name }); expect(context.comp.getValue('/name')).toBe(name); }); it('#setValue', () => { const name = 'new-asdf'; context.comp.setValue('/name', name); expect(context.comp.value.name).toBe(name); }); it('#setValue, shoule be throw error when invlaid path', () => { expect(() => { context.comp.setValue('/invalid-path', name); }).toThrow(); }); }); describe('[Custom Validator]', () => { it('with function and shoule be success when return a empty errors', () => { const s: SFSchema = { properties: { a: { type: 'string', ui: { validator: jasmine.createSpy().and.returnValue([{ keyword: 'required', message: 'a' }]), }, }, }, }; page.newSchema(s); expect(page.getProperty('/a').valid).toBe(false); }); it('with function', () => { const s: SFSchema = { properties: { a: { type: 'string', ui: { validator: jasmine.createSpy().and.returnValue([]), }, }, }, }; page.newSchema(s); expect(page.getProperty('/a').valid).toBe(true); }); it('with observable', () => { const s: SFSchema = { properties: { a: { type: 'string', ui: { validator: jasmine.createSpy().and.returnValue(of([{ keyword: 'required', message: 'a' }])), }, }, }, }; page.newSchema(s); expect(page.getProperty('/a').valid).toBe(false); }); it('shoule be throw error when non-include a message property', () => { expect(() => { const s: SFSchema = { properties: { a: { type: 'string', ui: { validator: jasmine.createSpy().and.returnValue([{ keyword: 'required' }]), }, }, }, }; page.newSchema(s); }).toThrowError(); }); it('shoule be support custom params in message', () => { const s: SFSchema = { properties: { a: { type: 'string', ui: { validator: () => [ { keyword: 'a', message: 'a-{id}-{invalid}', params: { id: 10, }, }, ], }, }, }, }; page.newSchema(s); expect(page.getProperty('/a').errors![0].message).toBe(`a-10-`); }); }); describe('[Custom Show Errors]', () => { it('shoule be re-error message via error property', () => { const s: SFSchema = { properties: { a: { type: 'string', ui: { errors: { required: 'REQUEST', }, }, }, }, required: ['a'], }; page.newSchema(s); expect(page.getProperty('/a').errors![0].message).toBe('REQUEST'); }); it('shoule be re-error message via error property and type is function', () => { const s: SFSchema = { properties: { a: { type: 'string', ui: { errors: { required: jasmine.createSpy().and.returnValue('A'), }, }, }, }, required: ['a'], }; page.newSchema(s); expect(page.getProperty('/a').errors![0].message).toBe('A'); expect((s.properties!.a.ui as any).errors.required).toHaveBeenCalled(); }); it('should be i18n', () => { const iconSrv = TestBed.inject(NzIconService); spyOn(iconSrv, 'getRenderedContent').and.returnValue(of()); const s: SFSchema = { properties: { a: { type: 'string', }, arr: { type: 'array', items: { type: 'object', properties: { name: { type: 'string', }, }, required: ['name'], }, }, }, required: ['a'], }; page.newSchema(s, undefined, { a: '', arr: [{ name: '' }] }); expect(page.getProperty('/a').errors![0].message).toBe(context.comp.locale.error.required); const i18n = TestBed.inject(OhayoLocaleService); i18n.setLocale(en_US); fixture.detectChanges(); expect(page.getProperty('/a').errors![0].message).toBe(context.comp.locale.error.required); expect(page.getProperty('/arr').errors![0].message).toBe(context.comp.locale.error.required); }); it('should be display required * when showRequired is true', () => { const s: SFSchema = { properties: { a: { type: 'string', ui: { showRequired: true, }, }, }, }; page.newSchema(s).checkCount('.ant-form-item-required', 1); }); }); }); describe('#mode', () => { beforeEach(() => { genModule(); ({ fixture, dl, context } = createTestContext(TestModeComponent)); }); it('should be auto 搜索 in submit', () => { context.mode = 'search'; createComp(); expect(page.getEl('.ant-btn-primary').textContent).toContain('搜索'); }); it('should be auto 保存 in submit', () => { context.mode = 'edit'; createComp(); expect(page.getEl('.ant-btn-primary').textContent).toContain('保存'); }); it('should be custom text of search', () => { context.mode = 'search'; context.button = { search: 'SEARCH', }; createComp(); expect(page.getEl('.ant-btn-primary').textContent).toContain('SEARCH'); }); it('should be custom text of edit', () => { context.mode = 'edit'; context.button = { edit: 'SAVE', }; createComp(); expect(page.getEl('.ant-btn-primary').textContent).toContain('SAVE'); }); }); describe('ACL', () => { beforeEach(() => genModule({ acl: true })); it('should working', fakeAsync(() => { ({ fixture, dl, context } = createTestContext(TestFormComponent)); createComp(); const acl = TestBed.inject<ACLService>(ACLService); acl.setFull(false); acl.setRole(['admin']); const s: SFSchema = { properties: { a: { type: 'string', ui: { acl: 'admin', }, }, }, required: ['a'], }; page.newSchema(s); page.checkUI('/a', 'hidden', false); acl.setRole(['user']); tick(); fixture.detectChanges(); page.checkUI('/a', 'hidden', true); })); }); describe('I18N', () => { beforeEach(() => genModule({ i18n: true })); it('should working', fakeAsync(() => { ({ fixture, dl, context } = createTestContext(TestFormComponent)); createComp(); const i18n = TestBed.inject(OHAYO_I18N_TOKEN) as OhayoI18NService; let lang = 'en'; spyOn(i18n, 'fanyi').and.callFake(((key: string) => { if (key === 'null') return null; return lang === 'en' ? key : `zh-${key}`; }) as any); const s: SFSchema = { properties: { a: { type: 'string', title: 'title', description: 'title', ui: { i18n: 'i18n', descriptionI18n: 'descriptionI18n', optionalHelp: { i18n: 'ohi18n', }, }, }, b: { type: 'string', title: 'a', ui: { i18n: 'null', }, }, }, }; page.newSchema(s); page .checkSchema('/a', 'title', 'i18n') .checkSchema('/b', 'title', 'null') .checkSchema('/a', 'description', 'descriptionI18n') .checkUI('/a', 'optionalHelp.text', 'ohi18n'); lang = 'zh'; i18n.use(lang); page .checkSchema('/a', 'title', 'zh-i18n') .checkSchema('/a', 'description', 'zh-descriptionI18n') .checkUI('/a', 'optionalHelp.text', 'zh-ohi18n'); })); }); }); @Component({ template: ` <sf [layout]="layout" #comp [schema]="schema" [ui]="ui" [button]="button" [mode]="mode" [loading]="loading"></sf> `, }) class TestModeComponent extends TestFormComponent { }