mirror of
https://github.com/valitydev/dashboard.git
synced 2024-11-06 02:25:23 +00:00
TD-625: Move to @vality/eslint-config (#128)
This commit is contained in:
parent
6eff441919
commit
73de0048b2
56
.eslintrc.js
56
.eslintrc.js
@ -1,57 +1,7 @@
|
||||
const rules = require('./tools/eslint-config/rules');
|
||||
|
||||
const baseTsRules = {
|
||||
parserOptions: {
|
||||
project: ['tsconfig.json'],
|
||||
createDefaultProgram: true,
|
||||
},
|
||||
extends: [
|
||||
'./tools/eslint-config/typescript',
|
||||
'./tools/eslint-config/angular',
|
||||
'./tools/eslint-config/lodash',
|
||||
'prettier',
|
||||
],
|
||||
rules: {
|
||||
...rules.createImportOrderRule({ internalPathsPattern: '@dsh/**' }),
|
||||
// TODO: pretenders for error
|
||||
'@typescript-eslint/no-unsafe-call': 'warn',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'warn',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'warn',
|
||||
'@typescript-eslint/no-unsafe-return': 'warn',
|
||||
'@typescript-eslint/no-misused-promises': 'warn',
|
||||
'@typescript-eslint/unbound-method': 'warn',
|
||||
'@typescript-eslint/restrict-plus-operands': 'warn',
|
||||
'@typescript-eslint/restrict-template-expressions': 'warn',
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: '@vality/eslint-config',
|
||||
overrides: [
|
||||
{
|
||||
...baseTsRules,
|
||||
files: ['*.ts'],
|
||||
rules: {
|
||||
...baseTsRules.rules,
|
||||
...rules.createAngularSelectorRules({ prefix: 'dsh' }),
|
||||
// TODO: pretenders for error
|
||||
'@typescript-eslint/no-floating-promises': 'warn',
|
||||
},
|
||||
},
|
||||
{
|
||||
...baseTsRules,
|
||||
files: ['*.spec.ts'],
|
||||
extends: [...baseTsRules.extends, './tools/eslint-config/jasmine'],
|
||||
},
|
||||
{
|
||||
files: ['*.html'],
|
||||
extends: ['plugin:@angular-eslint/template/recommended'],
|
||||
rules: {
|
||||
// TODO: pretenders for error
|
||||
'@angular-eslint/template/no-negated-async': 'warn',
|
||||
},
|
||||
},
|
||||
...require('@vality/eslint-config/configs').angular('dsh').overrides,
|
||||
...require('@vality/eslint-config/configs').importOrder(['@dsh/**']).overrides,
|
||||
],
|
||||
};
|
||||
|
2
.github/workflows/pr.yaml
vendored
2
.github/workflows/pr.yaml
vendored
@ -40,7 +40,7 @@ jobs:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}
|
||||
- name: Check
|
||||
run: npm run lint-cmd
|
||||
run: npm run lint
|
||||
i18n:
|
||||
name: Translation keys check
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -178,6 +178,12 @@
|
||||
"karmaConfig": "karma-ci.conf.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-eslint/builder:lint",
|
||||
"options": {
|
||||
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20420
package-lock.json
generated
20420
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@ -6,16 +6,15 @@
|
||||
"postinstall": "ngcc",
|
||||
"start": "ng serve --proxy-config proxy.conf.js --port 8000",
|
||||
"stage": "cross-env NODE_ENV=stage ng serve --proxy-config proxy.conf.js --port 8001 --configuration=stage",
|
||||
"fix": "npm run lint-fix && npm run prettier-fix",
|
||||
"fix": "npm run lint:fix && npm run prettier-fix",
|
||||
"build": "ng build && transloco-optimize dist/assets/i18n",
|
||||
"test": "ng test",
|
||||
"i18n:extract": "transloco-keys-manager extract",
|
||||
"i18n:check": "transloco-keys-manager find --emit-error-on-extra-keys",
|
||||
"coverage": "npx http-server -c-1 -o -p 9875 ./coverage",
|
||||
"lint-cmd": "eslint \"src/**/*.{ts,js,html}\" --max-warnings 1075",
|
||||
"lint": "npm run lint-cmd",
|
||||
"lint-fix": "npm run lint-cmd -- --fix",
|
||||
"lint-errors": "npm run lint-cmd -- --quiet",
|
||||
"lint": "ng lint --max-warnings=0",
|
||||
"lint:fix": "ng lint --fix",
|
||||
"lint:errors": "ng lint --quiet",
|
||||
"prettier-cmd": "prettier \"**/*.{html,js,ts,css,scss,md,json,prettierrc,svg,yaml,yml}\"",
|
||||
"prettier": "npm run prettier-cmd -- --check",
|
||||
"prettier-fix": "npm run prettier-cmd -- --write",
|
||||
@ -83,10 +82,7 @@
|
||||
"@angular-builders/custom-webpack": "15.0.0",
|
||||
"@angular-devkit/build-angular": "15.2.4",
|
||||
"@angular-eslint/builder": "15.2.1",
|
||||
"@angular-eslint/eslint-plugin": "15.2.1",
|
||||
"@angular-eslint/eslint-plugin-template": "15.2.1",
|
||||
"@angular-eslint/schematics": "15.2.1",
|
||||
"@angular-eslint/template-parser": "15.2.1",
|
||||
"@angular/cli": "15.2.4",
|
||||
"@angular/compiler-cli": "15.2.4",
|
||||
"@angular/language-service": "15.2.4",
|
||||
@ -100,19 +96,11 @@
|
||||
"@types/lodash-es": "4.17.6",
|
||||
"@types/moment": "2.13.0",
|
||||
"@types/prettier": "2.4.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.57.0",
|
||||
"@typescript-eslint/parser": "5.57.0",
|
||||
"@vality/eslint-config": "0.1.1-2a1b72f.0",
|
||||
"concurrently": "7.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.0.3",
|
||||
"eslint": "8.37.0",
|
||||
"eslint-config-prettier": "8.8.0",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-jasmine": "4.1.3",
|
||||
"eslint-plugin-jsdoc": "40.1.0",
|
||||
"eslint-plugin-prefer-arrow": "1.2.3",
|
||||
"eslint-plugin-unused-imports": "2.0.0",
|
||||
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
|
||||
"glob": "^7.1.6",
|
||||
"jasmine-core": "~3.7.0",
|
||||
"jasmine-marbles": "0.9.2",
|
||||
|
@ -18,6 +18,6 @@ export class DaDataService extends createApi(ApiDaDataService) {
|
||||
): Observable<SuggestionsByRequestType[T]> {
|
||||
const requestParams = { request: { daDataRequestType, ...params } };
|
||||
const request = this.requestDaData({ daDataParams: requestParams }) as Observable<ResponseByRequestType[T]>;
|
||||
return request.pipe(pluck('suggestions')) as Observable<any>;
|
||||
return request.pipe(pluck('suggestions')) as Observable<SuggestionsByRequestType[T]>;
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ type DeepOnlyMutable<T> = T extends object
|
||||
: T;
|
||||
type ApiArgs = [Injector];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type MethodParams<P extends Record<PropertyKey, any>, K extends PropertyKey> = RequiredKeys<Omit<P, K>> extends never
|
||||
? void | Overwrite<P, { [N in K]?: P[N] }>
|
||||
: Overwrite<P, { [N in K]?: P[N] }>;
|
||||
@ -30,9 +31,10 @@ type Method<M, P extends PropertyKey> = M extends (...args: unknown[]) => Observ
|
||||
* Don't use super with Api class methods because they were added with the object assign
|
||||
*/
|
||||
export function createApi<
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
T extends Record<PropertyKey, any> & { defaultHeaders: HttpHeaders },
|
||||
E extends (new (...args: any[]) => ApiExtension)[] = []
|
||||
>(apiClass: new (...args: any[]) => T, extensions: E = [] as E) {
|
||||
E extends (new (...args: unknown[]) => ApiExtension)[] = []
|
||||
>(apiClass: new (...args: unknown[]) => T, extensions: E = [] as E) {
|
||||
@Injectable()
|
||||
class Api {
|
||||
private api = this.injector.get<T>(apiClass);
|
||||
|
@ -1,3 +1,4 @@
|
||||
export interface ApiExtension {
|
||||
selector: (...args: any[]) => Record<PropertyKey, any>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
selector: (...args: unknown[]) => Record<PropertyKey, any>;
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ export class PartyIdPatchMethodService extends PartyIdExtension {
|
||||
this.selector().pipe(
|
||||
switchMap(({ partyID }) => {
|
||||
const newParams = cloneDeep(params);
|
||||
patch(newParams as any, partyID);
|
||||
return method(newParams as any);
|
||||
patch(newParams as unknown as P, partyID);
|
||||
return method(newParams as unknown as P);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -1,94 +0,0 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { TranslocoService } from '@ngneat/transloco';
|
||||
import { cold } from 'jasmine-marbles';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { anyNumber, anything, instance, mock, verify, when } from 'ts-mockito';
|
||||
|
||||
import { ClaimsService } from '@dsh/app/api/claim-management';
|
||||
import { OrgsService } from '@dsh/app/api/organizations';
|
||||
import { PartiesService } from '@dsh/app/api/payments';
|
||||
import { ApiShopsService } from '@dsh/app/api/shop';
|
||||
import { ErrorService } from '@dsh/app/shared';
|
||||
|
||||
import { BootstrapService } from './bootstrap.service';
|
||||
|
||||
describe('BootstrapService', () => {
|
||||
let service: BootstrapService;
|
||||
let mockApiShopsService: ApiShopsService;
|
||||
let mockClaimsService: ClaimsService;
|
||||
let mockCAPIPartiesService: PartiesService;
|
||||
let mockErrorService: ErrorService;
|
||||
let mockOrganizationsService: OrgsService;
|
||||
let mockTranslocoService: TranslocoService;
|
||||
|
||||
beforeEach(() => {
|
||||
mockApiShopsService = mock(ApiShopsService);
|
||||
mockClaimsService = mock(ClaimsService);
|
||||
mockCAPIPartiesService = mock(PartiesService);
|
||||
mockErrorService = mock(ErrorService);
|
||||
mockOrganizationsService = mock(OrgsService);
|
||||
mockTranslocoService = mock(TranslocoService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
BootstrapService,
|
||||
{ provide: ApiShopsService, useFactory: () => instance(mockApiShopsService) },
|
||||
{ provide: ClaimsService, useFactory: () => instance(mockClaimsService) },
|
||||
{ provide: PartiesService, useFactory: () => instance(mockCAPIPartiesService) },
|
||||
{ provide: ErrorService, useFactory: () => instance(mockErrorService) },
|
||||
{ provide: OrgsService, useFactory: () => instance(mockOrganizationsService) },
|
||||
{ provide: TranslocoService, useFactory: () => instance(mockTranslocoService) },
|
||||
],
|
||||
});
|
||||
service = TestBed.inject(BootstrapService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
when(mockCAPIPartiesService.getMyParty()).thenReturn(of('party' as any));
|
||||
when(mockClaimsService.createClaim(anything())).thenReturn(of('claim' as any));
|
||||
when(mockOrganizationsService.createOrg(anything())).thenReturn(of('org' as any));
|
||||
when(mockOrganizationsService.listOrgMembership(anyNumber())).thenReturn(of({ result: ['membership' as any] }));
|
||||
when(mockApiShopsService.shops$).thenReturn(of(['shop1' as any]));
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
// TODO fix unstable error
|
||||
xdescribe('bootstrap', () => {
|
||||
it('should be init party, org and shop', () => {
|
||||
service.bootstrap();
|
||||
expect(service.bootstrapped$).toBeObservable(cold('(a)', { a: true }));
|
||||
verify(mockCAPIPartiesService.getMyParty()).once();
|
||||
verify(mockOrganizationsService.createOrg(anything())).never();
|
||||
verify(mockClaimsService.createClaim(anything())).never();
|
||||
});
|
||||
|
||||
it('should be created org', () => {
|
||||
when(mockOrganizationsService.listOrgMembership(anyNumber())).thenReturn(of({ result: [] }));
|
||||
service.bootstrap();
|
||||
service.bootstrapped$.subscribe().unsubscribe();
|
||||
verify(mockOrganizationsService.createOrg(anything())).once();
|
||||
expect().nothing();
|
||||
});
|
||||
|
||||
it('should be created shop', () => {
|
||||
when(mockApiShopsService.shops$).thenReturn(of([]));
|
||||
service.bootstrap();
|
||||
service.bootstrapped$.subscribe().unsubscribe();
|
||||
verify(mockClaimsService.createClaim(anything())).once();
|
||||
expect().nothing();
|
||||
});
|
||||
|
||||
it('should be return error', () => {
|
||||
when(mockCAPIPartiesService.getMyParty()).thenReturn(throwError('error'));
|
||||
service.bootstrap();
|
||||
service.bootstrapped$.subscribe().unsubscribe();
|
||||
verify(mockErrorService.error(anything())).once();
|
||||
expect().nothing();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,8 +0,0 @@
|
||||
import get from 'lodash-es/get';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
export const filterByProp =
|
||||
<T>(path: import('lodash').PropertyPath, value: any) =>
|
||||
(s: Observable<T[]>) =>
|
||||
s.pipe(map((items) => items.filter((item) => get(item, path) === value)));
|
@ -1,6 +1,5 @@
|
||||
export * from './take-error';
|
||||
export * from './result-with-token';
|
||||
export * from './filter-by-prop';
|
||||
export * from './handle-null';
|
||||
export * from './progress';
|
||||
export * from './replace-error';
|
||||
|
@ -2,4 +2,5 @@ import * as moment from 'moment';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
export const mapToTimestamp = (s: Observable<any>): Observable<string> => s.pipe(map(() => moment().utc().format()));
|
||||
export const mapToTimestamp = (s: Observable<unknown>): Observable<string> =>
|
||||
s.pipe(map(() => moment().utc().format()));
|
||||
|
@ -4,7 +4,11 @@ import { catchError, distinctUntilChanged, map, startWith } from 'rxjs/operators
|
||||
/**
|
||||
* @deprecated use toProgress()
|
||||
*/
|
||||
export const progress = (start$: Observable<any>, end$: Observable<any>, startValue = false): Observable<boolean> =>
|
||||
export const progress = (
|
||||
start$: Observable<unknown>,
|
||||
end$: Observable<unknown>,
|
||||
startValue = false
|
||||
): Observable<boolean> =>
|
||||
merge(start$.pipe(map(() => true)), end$.pipe(map(() => false))).pipe(
|
||||
catchError(() => of(false)),
|
||||
startWith(startValue),
|
||||
|
@ -4,7 +4,7 @@ import { catchError, filter, pluck } from 'rxjs/operators';
|
||||
/**
|
||||
* @deprecated use toError()
|
||||
*/
|
||||
export class BasicError<T = any> {
|
||||
export class BasicError<T = unknown> {
|
||||
constructor(public error: T) {}
|
||||
}
|
||||
|
||||
@ -25,15 +25,15 @@ export function isPayload<T>(value: T | BasicError): value is T {
|
||||
/**
|
||||
* @deprecated use toError()
|
||||
*/
|
||||
export const replaceError = <T, E = any>(source: Observable<T>): Observable<T | BasicError<E>> =>
|
||||
export const replaceError = <T, E>(source: Observable<T>): Observable<T | BasicError<E>> =>
|
||||
source.pipe(catchError((value) => of(new BasicError(value))));
|
||||
|
||||
/**
|
||||
* @deprecated use toError()
|
||||
*/
|
||||
export const filterError = <E, T = any>(source: Observable<T | BasicError<E>>): Observable<E> =>
|
||||
export const filterError = <E, T>(source: Observable<T | BasicError<E>>): Observable<E> =>
|
||||
source.pipe(
|
||||
filter<BasicError>((value) => value instanceof BasicError),
|
||||
filter<BasicError<E>>((value) => value instanceof BasicError),
|
||||
pluck('error')
|
||||
);
|
||||
|
||||
|
@ -3,7 +3,7 @@ import localeRu from '@angular/common/locales/ru';
|
||||
|
||||
import { Language } from './languages';
|
||||
|
||||
export const ANGULAR_LOCALE_DATA: { [language in Language]: any } = {
|
||||
export const ANGULAR_LOCALE_DATA: { [language in Language]: unknown } = {
|
||||
ru: localeRu,
|
||||
en: localeEn,
|
||||
};
|
||||
|
@ -1,166 +0,0 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { Router } from '@angular/router';
|
||||
import { Claim } from '@vality/swag-claim-management';
|
||||
import { of } from 'rxjs';
|
||||
import { deepEqual, mock, verify, when } from 'ts-mockito';
|
||||
|
||||
import { ShopCreationService } from '@dsh/app/shared/components/shop-creation';
|
||||
import { QueryParamsService } from '@dsh/app/shared/services/query-params';
|
||||
import { provideMockService } from '@dsh/app/shared/tests';
|
||||
import { getTranslocoModule } from '@dsh/app/shared/tests/get-transloco-module';
|
||||
import { LastUpdatedModule } from '@dsh/components/indicators/last-updated/last-updated.module';
|
||||
|
||||
import { ClaimsSearchFiltersSearchParams } from './claims-search-filters/claims-search-filters-search-params';
|
||||
import { ClaimsComponent } from './claims.component';
|
||||
import { FetchClaimsService } from './services/fetch-claims/fetch-claims.service';
|
||||
import { generateMockClaim } from './tests/generate-mock-claim';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-claims-search-filters',
|
||||
template: '',
|
||||
})
|
||||
class MockClaimsSearchFiltersComponent {
|
||||
@Input() initParams: ClaimsSearchFiltersSearchParams;
|
||||
|
||||
@Output()
|
||||
searchParamsChanges = new EventEmitter<ClaimsSearchFiltersSearchParams>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-claims-list',
|
||||
template: '',
|
||||
})
|
||||
class MockClaimsListComponent {
|
||||
@Input() claimList: Claim[];
|
||||
@Input() lastUpdated: string;
|
||||
@Input() isLoading: boolean;
|
||||
@Input() hasMore: boolean;
|
||||
@Input() expandedId: number;
|
||||
|
||||
@Output() refresh = new EventEmitter<void>();
|
||||
@Output() showMore = new EventEmitter<void>();
|
||||
@Output() goToClaimDetails: EventEmitter<number> = new EventEmitter();
|
||||
}
|
||||
|
||||
describe('ClaimsComponent', () => {
|
||||
let component: ClaimsComponent;
|
||||
let fixture: ComponentFixture<ClaimsComponent>;
|
||||
let mockFetchClaimsService: FetchClaimsService;
|
||||
let mockRouter: Router;
|
||||
let mockQueryParamsService: QueryParamsService<any>;
|
||||
let mockShopCreationService: ShopCreationService;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRouter = mock(Router);
|
||||
mockFetchClaimsService = mock(FetchClaimsService);
|
||||
mockQueryParamsService = mock(QueryParamsService);
|
||||
mockShopCreationService = mock(ShopCreationService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
when(mockFetchClaimsService.searchResult$).thenReturn(of([generateMockClaim(1), generateMockClaim(2)]));
|
||||
when(mockFetchClaimsService.isLoading$).thenReturn(of(false));
|
||||
when(mockFetchClaimsService.hasMore$).thenReturn(of(false));
|
||||
when(mockFetchClaimsService.lastUpdated$).thenReturn(of());
|
||||
});
|
||||
|
||||
async function configureTestingModule() {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
getTranslocoModule(),
|
||||
NoopAnimationsModule,
|
||||
LastUpdatedModule,
|
||||
FlexLayoutModule,
|
||||
HttpClientTestingModule,
|
||||
],
|
||||
declarations: [ClaimsComponent, MockClaimsSearchFiltersComponent, MockClaimsListComponent],
|
||||
providers: [
|
||||
provideMockService(FetchClaimsService, mockFetchClaimsService),
|
||||
provideMockService(Router, mockRouter),
|
||||
provideMockService(QueryParamsService, mockQueryParamsService),
|
||||
provideMockService(ShopCreationService, mockShopCreationService),
|
||||
],
|
||||
})
|
||||
.overrideComponent(ClaimsComponent, {
|
||||
set: {
|
||||
providers: [],
|
||||
},
|
||||
})
|
||||
.compileComponents();
|
||||
}
|
||||
|
||||
async function createComponent() {
|
||||
await configureTestingModule();
|
||||
|
||||
fixture = TestBed.createComponent(ClaimsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
describe('creation', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshList', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('should call refresh data', () => {
|
||||
when(mockFetchClaimsService.refresh()).thenReturn();
|
||||
|
||||
component.refresh();
|
||||
|
||||
verify(mockFetchClaimsService.refresh()).once();
|
||||
expect().nothing();
|
||||
});
|
||||
});
|
||||
|
||||
describe('requestNextPage', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('should call fetch more data', () => {
|
||||
when(mockFetchClaimsService.fetchMore()).thenReturn();
|
||||
|
||||
component.fetchMore();
|
||||
|
||||
verify(mockFetchClaimsService.fetchMore()).once();
|
||||
expect().nothing();
|
||||
});
|
||||
});
|
||||
|
||||
describe('filtersChanged', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('should request list using filters data', () => {
|
||||
const filtersData = {
|
||||
claimID: 1,
|
||||
};
|
||||
|
||||
component.search(filtersData);
|
||||
|
||||
verify(
|
||||
mockFetchClaimsService.search(
|
||||
deepEqual({
|
||||
claimID: filtersData.claimID,
|
||||
})
|
||||
)
|
||||
);
|
||||
expect().nothing();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,54 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { anything, deepEqual, mock, verify, when } from 'ts-mockito';
|
||||
|
||||
import { OrgsService } from '@dsh/app/api/organizations';
|
||||
import { ErrorService } from '@dsh/app/shared';
|
||||
import { provideMockService } from '@dsh/app/shared/tests';
|
||||
|
||||
import { AcceptInvitationComponent } from './accept-invitation.component';
|
||||
|
||||
describe('AcceptInvitationComponent', () => {
|
||||
let fixture: ComponentFixture<AcceptInvitationComponent>;
|
||||
let component: AcceptInvitationComponent;
|
||||
let mockRoute: ActivatedRoute;
|
||||
let mockOrganizationsService: OrgsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockRoute = mock(ActivatedRoute);
|
||||
when(mockRoute.params).thenReturn(of({ token: '123' } as any));
|
||||
|
||||
mockOrganizationsService = mock(OrgsService);
|
||||
when(mockOrganizationsService.joinOrg(anything())).thenReturn(of({} as any));
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CommonModule, RouterTestingModule.withRoutes([])],
|
||||
declarations: [AcceptInvitationComponent],
|
||||
providers: [
|
||||
provideMockService(OrgsService, mockOrganizationsService),
|
||||
provideMockService(ErrorService),
|
||||
provideMockService(ActivatedRoute, mockRoute),
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AcceptInvitationComponent);
|
||||
component = fixture.debugElement.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('accept method', () => {
|
||||
it('should be join to org', () => {
|
||||
component.accept();
|
||||
verify(mockOrganizationsService.joinOrg(deepEqual({ invitation: '123' }))).once();
|
||||
expect().nothing();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,94 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { InvitationListResult } from '@vality/swag-organizations';
|
||||
import { cold } from 'jasmine-marbles';
|
||||
import { of } from 'rxjs';
|
||||
import { anything, mock, verify, when, anyString } from 'ts-mockito';
|
||||
|
||||
import { OrgsService } from '@dsh/app/api/organizations';
|
||||
import { MOCK_INVITATION } from '@dsh/app/api/organizations/tests/mock-invitation';
|
||||
import { MOCK_ORG } from '@dsh/app/api/organizations/tests/mock-org';
|
||||
import { DIALOG_CONFIG } from '@dsh/app/sections/tokens';
|
||||
import { ErrorService } from '@dsh/app/shared';
|
||||
import { BaseDialogResponseStatus } from '@dsh/app/shared/components/dialog/base-dialog';
|
||||
import { provideMockService, provideMockToken } from '@dsh/app/shared/tests';
|
||||
|
||||
import { InvitationsComponent } from './invitations.component';
|
||||
|
||||
describe('InvitationsComponent', () => {
|
||||
let component: InvitationsComponent;
|
||||
let fixture: ComponentFixture<InvitationsComponent>;
|
||||
let mockRoute: ActivatedRoute;
|
||||
let mockOrganizationsService: OrgsService;
|
||||
let mockDialog: MatDialog;
|
||||
|
||||
const mockInvitationsResult: InvitationListResult = {
|
||||
result: new Array(5).fill(MOCK_INVITATION),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
mockRoute = mock(ActivatedRoute);
|
||||
mockOrganizationsService = mock(OrgsService);
|
||||
mockDialog = mock(MatDialog);
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [InvitationsComponent],
|
||||
providers: [
|
||||
provideMockToken(DIALOG_CONFIG, { small: {}, medium: {}, large: {} }),
|
||||
provideMockService(OrgsService, mockOrganizationsService),
|
||||
provideMockService(ErrorService),
|
||||
provideMockService(ActivatedRoute, mockRoute),
|
||||
provideMockService(MatDialog, mockDialog),
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
when(mockRoute.params).thenReturn(of({ orgId: MOCK_ORG.id }));
|
||||
when(mockOrganizationsService.getOrg(MOCK_ORG.id)).thenReturn(of(MOCK_ORG));
|
||||
when(mockOrganizationsService.listInvitations(MOCK_ORG.id, anyString())).thenReturn(of(mockInvitationsResult));
|
||||
|
||||
fixture = TestBed.createComponent(InvitationsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('init', () => {
|
||||
it('should load organization$', () => {
|
||||
const expected$ = cold('(a|)', { a: MOCK_ORG });
|
||||
component.organization$.subscribe();
|
||||
verify(mockOrganizationsService.getOrg(MOCK_ORG.id)).once();
|
||||
expect(component.organization$).toBeObservable(expected$);
|
||||
});
|
||||
|
||||
it('should load invitations$', () => {
|
||||
const expected$ = cold('(a)', { a: mockInvitationsResult.result });
|
||||
expect(component.invitations$).toBeObservable(expected$);
|
||||
});
|
||||
});
|
||||
|
||||
describe('refresh', () => {
|
||||
it('should load invitations$', () => {
|
||||
component.invitations$.subscribe();
|
||||
component.refresh();
|
||||
verify(mockOrganizationsService.listInvitations(MOCK_ORG.id, anyString())).twice();
|
||||
const expected$ = cold('(a)', { a: mockInvitationsResult.result });
|
||||
expect(component.invitations$).toBeObservable(expected$);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createInvitation', () => {
|
||||
it('should be created', () => {
|
||||
when(mockDialog.open(anything(), anything())).thenReturn({
|
||||
afterClosed: () => of(BaseDialogResponseStatus.Success),
|
||||
} as any);
|
||||
component.invitations$.subscribe();
|
||||
component.createInvitation();
|
||||
verify(mockOrganizationsService.listInvitations(MOCK_ORG.id, anyString())).twice();
|
||||
expect().nothing();
|
||||
});
|
||||
});
|
||||
});
|
@ -27,7 +27,7 @@ describe('MemberComponent', () => {
|
||||
imports: [MatDialogModule],
|
||||
declarations: [HostComponent, MemberComponent],
|
||||
providers: [
|
||||
provideMockToken(DIALOG_CONFIG, {} as any),
|
||||
provideMockToken(DIALOG_CONFIG, {} as unknown),
|
||||
provideMockService(OrganizationManagementService),
|
||||
provideMockService(OrgsService),
|
||||
provideMockService(NotificationService),
|
||||
|
@ -1,56 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute, RouterModule } from '@angular/router';
|
||||
import { TranslocoTestingModule } from '@ngneat/transloco';
|
||||
import { cold } from 'jasmine-marbles';
|
||||
import { of } from 'rxjs';
|
||||
import { anyString, mock, verify, when } from 'ts-mockito';
|
||||
|
||||
import { OrgsService } from '@dsh/app/api/organizations';
|
||||
import { MOCK_ORG } from '@dsh/app/api/organizations/tests/mock-org';
|
||||
import { provideMockService } from '@dsh/app/shared/tests';
|
||||
|
||||
import { OrganizationDetailsComponent } from './organization-details.component';
|
||||
|
||||
describe('OrganizationDetailsComponent', () => {
|
||||
let component: OrganizationDetailsComponent;
|
||||
let fixture: ComponentFixture<OrganizationDetailsComponent>;
|
||||
let mockOrganizationsService: OrgsService;
|
||||
let mockActivatedRoute: ActivatedRoute;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockOrganizationsService = mock(OrgsService);
|
||||
mockActivatedRoute = mock(ActivatedRoute);
|
||||
|
||||
when(mockOrganizationsService.getOrg(anyString())).thenReturn(of(MOCK_ORG));
|
||||
when(mockOrganizationsService.getOrg(MOCK_ORG.id)).thenReturn(of(MOCK_ORG));
|
||||
when(mockActivatedRoute.params).thenReturn(of({ orgId: MOCK_ORG.id }));
|
||||
when(mockActivatedRoute.snapshot).thenReturn({} as any);
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslocoTestingModule.withLangs({}, { missingHandler: { logMissingKey: false } }),
|
||||
RouterModule.forRoot([]),
|
||||
],
|
||||
declarations: [OrganizationDetailsComponent],
|
||||
providers: [
|
||||
provideMockService(OrgsService, mockOrganizationsService),
|
||||
provideMockService(ActivatedRoute, mockActivatedRoute),
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(OrganizationDetailsComponent);
|
||||
component = fixture.debugElement.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('should be init', () => {
|
||||
it('organization$', () => {
|
||||
expect(component.organization$).toBeObservable(cold('(a|)', { a: MOCK_ORG }));
|
||||
verify(mockOrganizationsService.getOrg(MOCK_ORG.id)).once();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,123 +0,0 @@
|
||||
import { Component, DebugElement } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { MatDialog, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslocoTestingModule } from '@ngneat/transloco';
|
||||
import { of } from 'rxjs';
|
||||
import { anyString, anything, mock, verify, when } from 'ts-mockito';
|
||||
|
||||
import { OrgsService } from '@dsh/app/api/organizations';
|
||||
import { MOCK_MEMBER } from '@dsh/app/api/organizations/tests/mock-member';
|
||||
import { MOCK_ORG } from '@dsh/app/api/organizations/tests/mock-org';
|
||||
import { DIALOG_CONFIG } from '@dsh/app/sections/tokens';
|
||||
import { KeycloakTokenInfoService } from '@dsh/app/shared';
|
||||
import { BaseDialogResponseStatus } from '@dsh/app/shared/components/dialog/base-dialog';
|
||||
import { OrganizationRolesModule } from '@dsh/app/shared/components/organization-roles';
|
||||
import { ErrorModule, ErrorService } from '@dsh/app/shared/services/error';
|
||||
import { FetchOrganizationsService } from '@dsh/app/shared/services/fetch-organizations';
|
||||
import { NotificationModule, NotificationService } from '@dsh/app/shared/services/notification';
|
||||
import { OrganizationManagementService } from '@dsh/app/shared/services/organization-management/organization-management.service';
|
||||
import { provideMockService, provideMockToken } from '@dsh/app/shared/tests';
|
||||
import { DetailsItemModule } from '@dsh/components/layout';
|
||||
|
||||
import { OrganizationComponent } from './organization.component';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-host',
|
||||
template: `<dsh-organization [organization]="organization"></dsh-organization>`,
|
||||
})
|
||||
class HostComponent {
|
||||
organization = MOCK_ORG;
|
||||
}
|
||||
|
||||
describe('OrganizationComponent', () => {
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
let component: OrganizationComponent;
|
||||
let debugElement: DebugElement;
|
||||
let mockOrganizationManagementService: OrganizationManagementService;
|
||||
let mockOrganizationsService: OrgsService;
|
||||
let mockDialog: MatDialog;
|
||||
let mockNotificationService: NotificationService;
|
||||
let mockFetchOrganizationsService: FetchOrganizationsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockOrganizationManagementService = mock(OrganizationManagementService);
|
||||
mockOrganizationsService = mock(OrgsService);
|
||||
mockDialog = mock(MatDialog);
|
||||
mockNotificationService = mock(NotificationService);
|
||||
mockFetchOrganizationsService = mock(FetchOrganizationsService);
|
||||
|
||||
when(mockOrganizationManagementService.currentMember$).thenReturn(of(MOCK_MEMBER));
|
||||
when(mockOrganizationManagementService.isOrganizationOwner$).thenReturn(of(true));
|
||||
when(mockOrganizationsService.cancelOrgMembership(anyString())).thenReturn(of(null));
|
||||
when(mockOrganizationsService.listOrgMembers(anyString())).thenReturn(
|
||||
of({ result: new Array(15).fill(MOCK_MEMBER) })
|
||||
);
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslocoTestingModule.withLangs({}, { missingHandler: { logMissingKey: false } }),
|
||||
FlexLayoutModule,
|
||||
DetailsItemModule,
|
||||
MatDialogModule,
|
||||
NotificationModule,
|
||||
ErrorModule,
|
||||
OrganizationRolesModule,
|
||||
NoopAnimationsModule,
|
||||
MatDividerModule,
|
||||
],
|
||||
declarations: [HostComponent, OrganizationComponent],
|
||||
providers: [
|
||||
provideMockService(OrganizationManagementService, mockOrganizationManagementService),
|
||||
provideMockToken(DIALOG_CONFIG, { small: {}, medium: {}, large: {} }),
|
||||
provideMockService(OrgsService, mockOrganizationsService),
|
||||
provideMockService(NotificationService, mockNotificationService),
|
||||
provideMockService(ErrorService),
|
||||
provideMockService(FetchOrganizationsService, mockFetchOrganizationsService),
|
||||
provideMockService(MatDialog, mockDialog),
|
||||
provideMockService(KeycloakTokenInfoService),
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HostComponent);
|
||||
debugElement = fixture.debugElement.query(By.directive(OrganizationComponent));
|
||||
component = debugElement.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be init', () => {
|
||||
expect(component.organization).toBe(MOCK_ORG);
|
||||
});
|
||||
|
||||
describe('leave', () => {
|
||||
it('should be leave', () => {
|
||||
when(mockDialog.open(anything())).thenReturn({
|
||||
afterClosed: () => of('confirm'),
|
||||
} as MatDialogRef<any>);
|
||||
component.leave();
|
||||
verify(mockDialog.open(anything())).once();
|
||||
verify(mockOrganizationsService.cancelOrgMembership(MOCK_ORG.id)).once();
|
||||
verify(mockNotificationService.success()).once();
|
||||
expect().nothing();
|
||||
});
|
||||
});
|
||||
|
||||
describe('rename', () => {
|
||||
it('should be renamed', () => {
|
||||
when(mockDialog.open(anything(), anything())).thenReturn({
|
||||
afterClosed: () => of(BaseDialogResponseStatus.Success),
|
||||
} as MatDialogRef<any>);
|
||||
component.rename();
|
||||
verify(mockDialog.open(anything(), anything())).once();
|
||||
verify(mockFetchOrganizationsService.refresh()).once();
|
||||
expect().nothing();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,87 +0,0 @@
|
||||
import { Component, DebugElement } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TranslocoTestingModule } from '@ngneat/transloco';
|
||||
import { of } from 'rxjs';
|
||||
import { anything, instance, mock, verify, when } from 'ts-mockito';
|
||||
|
||||
import { BaseDialogResponseStatus } from '@dsh/app/shared/components/dialog/base-dialog';
|
||||
import { FetchOrganizationsService } from '@dsh/app/shared/services/fetch-organizations';
|
||||
import { EmptySearchResultModule } from '@dsh/components/empty-search-result';
|
||||
import { IndicatorsModule } from '@dsh/components/indicators';
|
||||
import { ScrollUpModule } from '@dsh/components/navigation';
|
||||
|
||||
import { OrganizationsComponent } from './organizations.component';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-host',
|
||||
template: `<dsh-organizations></dsh-organizations>`,
|
||||
})
|
||||
class HostComponent {}
|
||||
|
||||
describe('OrganizationsComponent', () => {
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
let debugElement: DebugElement;
|
||||
let component: OrganizationsComponent;
|
||||
let mockFetchOrganizationsService: FetchOrganizationsService;
|
||||
let mockDialog: MatDialog;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockFetchOrganizationsService = mock(FetchOrganizationsService);
|
||||
mockDialog = mock(MatDialog);
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
ScrollUpModule,
|
||||
IndicatorsModule,
|
||||
EmptySearchResultModule,
|
||||
TranslocoTestingModule.withLangs({}, { missingHandler: { logMissingKey: false } }),
|
||||
],
|
||||
declarations: [HostComponent, OrganizationsComponent],
|
||||
providers: [
|
||||
{ provide: FetchOrganizationsService, useValue: instance(mockFetchOrganizationsService) },
|
||||
{ provide: MatDialog, useValue: instance(mockDialog) },
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HostComponent);
|
||||
debugElement = fixture.debugElement.query(By.directive(OrganizationsComponent));
|
||||
component = debugElement.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be init', () => {
|
||||
verify(mockFetchOrganizationsService.search()).once();
|
||||
expect().nothing();
|
||||
});
|
||||
|
||||
describe('createOrganization', () => {
|
||||
afterEach(() => {
|
||||
expect().nothing();
|
||||
});
|
||||
|
||||
it('success', () => {
|
||||
when(mockDialog.open(anything())).thenReturn({
|
||||
afterClosed: () => of(BaseDialogResponseStatus.Success),
|
||||
} as MatDialogRef<any>);
|
||||
component.createOrganization();
|
||||
verify(mockDialog.open(anything())).once();
|
||||
verify(mockFetchOrganizationsService.refresh()).once();
|
||||
});
|
||||
|
||||
it('cancelled', () => {
|
||||
when(mockDialog.open(anything())).thenReturn({
|
||||
afterClosed: () => of(BaseDialogResponseStatus.Cancelled),
|
||||
} as MatDialogRef<any>);
|
||||
component.createOrganization();
|
||||
verify(mockDialog.open(anything())).once();
|
||||
verify(mockFetchOrganizationsService.refresh()).never();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,8 +1,8 @@
|
||||
export interface Series {
|
||||
name?: string;
|
||||
data: {
|
||||
x: any;
|
||||
y: any;
|
||||
x: unknown;
|
||||
y: unknown;
|
||||
fillColor?: string;
|
||||
strokeColor?: string;
|
||||
}[];
|
||||
|
@ -45,7 +45,7 @@ export class CreateInvoiceTemplateService {
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
invoiceTemplateAndToken$: Observable<InvoiceTemplateAndToken>;
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
errors$: Observable<any>;
|
||||
errors$: Observable<unknown>;
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
isLoading$: Observable<boolean>;
|
||||
|
||||
|
@ -23,9 +23,9 @@ export class WebhookEventsComponent {
|
||||
get events(): InvoicesTopic.EventTypesEnum[] | CustomersTopic.EventTypesEnum[] {
|
||||
switch (this.scope.topic) {
|
||||
case 'InvoicesTopic':
|
||||
return (this.scope as any as InvoicesTopic).eventTypes;
|
||||
return (this.scope as unknown as InvoicesTopic).eventTypes;
|
||||
case 'CustomersTopic':
|
||||
return (this.scope as any as CustomersTopic).eventTypes;
|
||||
return (this.scope as unknown as CustomersTopic).eventTypes;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { PaymentInstitution } from '@vality/swag-payments';
|
||||
import isEmpty from 'lodash-es/isEmpty';
|
||||
import negate from 'lodash-es/negate';
|
||||
// eslint-disable-next-line you-dont-need-lodash-underscore/omit
|
||||
import omit from 'lodash-es/omit';
|
||||
import pick from 'lodash-es/pick';
|
||||
import { defer, ReplaySubject, BehaviorSubject, combineLatest } from 'rxjs';
|
||||
|
@ -13,7 +13,7 @@ const toInvoiceTableData = (
|
||||
amount,
|
||||
currency,
|
||||
status,
|
||||
createdAt: createdAt as any,
|
||||
createdAt,
|
||||
invoiceID: id,
|
||||
shopName: getShopNameById(s, shopID),
|
||||
product,
|
||||
|
@ -6,7 +6,6 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { Shop, PaymentInstitution } from '@vality/swag-payments';
|
||||
import isEmpty from 'lodash-es/isEmpty';
|
||||
import negate from 'lodash-es/negate';
|
||||
// eslint-disable-next-line you-dont-need-lodash-underscore/omit
|
||||
import omit from 'lodash-es/omit';
|
||||
import pick from 'lodash-es/pick';
|
||||
import { defer, ReplaySubject, BehaviorSubject, combineLatest } from 'rxjs';
|
||||
|
@ -1,192 +0,0 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { PaymentSearchResult } from '@vality/swag-anapi-v2';
|
||||
import { of } from 'rxjs';
|
||||
import { instance, mock, verify, when } from 'ts-mockito';
|
||||
|
||||
import { PaymentInstitutionRealm } from '@dsh/app/api/model';
|
||||
import { QueryParamsService } from '@dsh/app/shared/services/query-params';
|
||||
import { provideMockService } from '@dsh/app/shared/tests';
|
||||
import { getTranslocoModule } from '@dsh/app/shared/tests/get-transloco-module';
|
||||
import { LastUpdatedModule } from '@dsh/components/indicators/last-updated/last-updated.module';
|
||||
|
||||
import { PaymentsComponent } from './payments.component';
|
||||
import { FetchPaymentsService } from './services/fetch-payments/fetch-payments.service';
|
||||
import { PaymentsExpandedIdManager } from './services/payments-expanded-id-manager/payments-expanded-id-manager.service';
|
||||
import { generateMockPayment } from './tests/generate-mock-payment';
|
||||
import { PaymentInstitutionRealmService, RealmMixService } from '../../services';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-payments-filters',
|
||||
template: '',
|
||||
})
|
||||
class MockPaymentsFiltersComponent {
|
||||
@Input() realm;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-payments-panels',
|
||||
template: '',
|
||||
})
|
||||
class MockPaymentsPanelsComponent {
|
||||
@Input() list: PaymentSearchResult[];
|
||||
@Input() isLoading: boolean;
|
||||
@Input() hasMore: boolean;
|
||||
@Input() expandedId: number;
|
||||
|
||||
@Output() showMore = new EventEmitter<void>();
|
||||
@Output() expandedIdChanged = new EventEmitter<number>();
|
||||
}
|
||||
|
||||
describe('PaymentsComponent', () => {
|
||||
let component: PaymentsComponent;
|
||||
let fixture: ComponentFixture<PaymentsComponent>;
|
||||
let mockActivatedRoute: ActivatedRoute;
|
||||
let mockPaymentsExpandedIdManager: PaymentsExpandedIdManager;
|
||||
let mockPaymentsService: FetchPaymentsService;
|
||||
let mockPaymentInstitutionRealmService: PaymentInstitutionRealmService;
|
||||
let mockRealmMixinService: RealmMixService<any>;
|
||||
let mockQueryParamsService: QueryParamsService<any>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockActivatedRoute = mock(ActivatedRoute);
|
||||
mockPaymentsExpandedIdManager = mock(PaymentsExpandedIdManager);
|
||||
mockPaymentsService = mock(FetchPaymentsService);
|
||||
mockPaymentInstitutionRealmService = mock(PaymentInstitutionRealmService);
|
||||
mockRealmMixinService = mock(RealmMixService);
|
||||
mockQueryParamsService = mock(QueryParamsService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const date = new Date();
|
||||
when(mockPaymentsService.paymentsList$).thenReturn(
|
||||
of([
|
||||
generateMockPayment({
|
||||
statusChangedAt: date,
|
||||
id: 'payment_id_0',
|
||||
}),
|
||||
generateMockPayment({
|
||||
statusChangedAt: date,
|
||||
id: 'payment_id_1',
|
||||
}),
|
||||
])
|
||||
);
|
||||
when(mockPaymentsService.isLoading$).thenReturn(of(false));
|
||||
when(mockPaymentsService.hasMore$).thenReturn(of(false));
|
||||
when(mockPaymentsService.lastUpdated$).thenReturn(of());
|
||||
when(mockPaymentInstitutionRealmService.realm$).thenReturn(of());
|
||||
when(mockRealmMixinService.mixedValue$).thenReturn(of());
|
||||
});
|
||||
|
||||
async function configureTestingModule() {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
getTranslocoModule(),
|
||||
NoopAnimationsModule,
|
||||
LastUpdatedModule,
|
||||
FlexLayoutModule,
|
||||
HttpClientTestingModule,
|
||||
],
|
||||
declarations: [PaymentsComponent, MockPaymentsFiltersComponent, MockPaymentsPanelsComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useFactory: () => instance(mockActivatedRoute),
|
||||
},
|
||||
{
|
||||
provide: PaymentsExpandedIdManager,
|
||||
useFactory: () => instance(mockPaymentsExpandedIdManager),
|
||||
},
|
||||
{
|
||||
provide: FetchPaymentsService,
|
||||
useFactory: () => instance(mockPaymentsService),
|
||||
},
|
||||
{
|
||||
provide: PaymentInstitutionRealmService,
|
||||
useFactory: () => instance(mockPaymentInstitutionRealmService),
|
||||
},
|
||||
{
|
||||
provide: RealmMixService,
|
||||
useFactory: () => instance(mockRealmMixinService),
|
||||
},
|
||||
provideMockService(QueryParamsService, mockQueryParamsService),
|
||||
],
|
||||
})
|
||||
.overrideComponent(PaymentsComponent, {
|
||||
set: {
|
||||
providers: [],
|
||||
},
|
||||
})
|
||||
.compileComponents();
|
||||
}
|
||||
|
||||
async function createComponent() {
|
||||
await configureTestingModule();
|
||||
|
||||
fixture = TestBed.createComponent(PaymentsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
when(mockActivatedRoute.params).thenReturn(of({ realm: PaymentInstitutionRealm.Live }));
|
||||
when(mockPaymentsExpandedIdManager.expandedId$).thenReturn(of(1));
|
||||
});
|
||||
|
||||
describe('creation', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshList', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('should call refresh data', () => {
|
||||
when(mockPaymentsService.refresh()).thenReturn();
|
||||
|
||||
component.refreshList();
|
||||
|
||||
verify(mockPaymentsService.refresh()).once();
|
||||
expect().nothing();
|
||||
});
|
||||
});
|
||||
|
||||
describe('requestNextPage', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('should call fetch more data', () => {
|
||||
when(mockPaymentsService.fetchMore()).thenReturn();
|
||||
|
||||
component.requestNextPage();
|
||||
|
||||
verify(mockPaymentsService.fetchMore()).once();
|
||||
expect().nothing();
|
||||
});
|
||||
});
|
||||
|
||||
describe('expandedIdChange', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('should call expanded id manager change method with provided id', () => {
|
||||
component.expandedIdChange(4);
|
||||
|
||||
verify(mockPaymentsExpandedIdManager.expandedIdChange(4)).once();
|
||||
expect().nothing();
|
||||
});
|
||||
});
|
||||
});
|
@ -2,7 +2,7 @@ import { PaymentSearchResult } from '@vality/swag-anapi-v2';
|
||||
|
||||
// function was made to simplify to compare test dates
|
||||
function makeCurrentDate(): Date {
|
||||
return new Date().toDateString() as any;
|
||||
return new Date().toDateString() as unknown as Date;
|
||||
}
|
||||
|
||||
export function generateMockPayment(data: Partial<PaymentSearchResult> = {}): PaymentSearchResult {
|
||||
|
@ -6,7 +6,6 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { Shop } from '@vality/swag-payments';
|
||||
import isEmpty from 'lodash-es/isEmpty';
|
||||
import negate from 'lodash-es/negate';
|
||||
// eslint-disable-next-line you-dont-need-lodash-underscore/omit
|
||||
import omit from 'lodash-es/omit';
|
||||
import pick from 'lodash-es/pick';
|
||||
import { combineLatest, defer, BehaviorSubject } from 'rxjs';
|
||||
|
@ -52,7 +52,7 @@ export class CreatePayoutDialogComponent implements OnInit {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
create(formValue: any) {
|
||||
create(formValue: { shopID: string; payoutToolID: string; amount: number }) {
|
||||
this.createPayoutDialogService.createPayout(formValue);
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import { toPayoutParams } from './to-payout-params';
|
||||
export class CreatePayoutDialogService {
|
||||
private currentShopID$ = new Subject<string>();
|
||||
|
||||
private create$: Subject<any> = new Subject();
|
||||
private create$ = new Subject<{ shopID: string; payoutToolID: string; amount: number }>();
|
||||
private loading$ = new BehaviorSubject(false);
|
||||
private error$ = new Subject<void>();
|
||||
private created$ = new Subject();
|
||||
@ -77,7 +77,7 @@ export class CreatePayoutDialogService {
|
||||
this.currentShopID$.next(id);
|
||||
}
|
||||
|
||||
createPayout(formValue: any) {
|
||||
createPayout(formValue: { shopID: string; payoutToolID: string; amount: number }) {
|
||||
this.create$.next(formValue);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,10 @@ import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { toMinor } from '../../../../../utils';
|
||||
|
||||
export const toPayoutParams = ({ shopID, payoutToolID, amount }: any, currency: string): PayoutParams => {
|
||||
export const toPayoutParams = (
|
||||
{ shopID, payoutToolID, amount }: { shopID: string; payoutToolID: string; amount: number },
|
||||
currency: string
|
||||
): PayoutParams => {
|
||||
return {
|
||||
id: uuid(),
|
||||
shopID,
|
||||
|
@ -47,7 +47,7 @@ export class CreateReportDialogComponent implements OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
create(formValue: any) {
|
||||
create(formValue: unknown) {
|
||||
this.createReportDialogService.create(formValue);
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ export class CreateReportDialogService {
|
||||
});
|
||||
}
|
||||
|
||||
create(formValue: any) {
|
||||
create(formValue: unknown) {
|
||||
this.create$.next(formValue);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import { ShopItem } from '../../types/shop-item';
|
||||
export class ShopsFiltersService {
|
||||
filterShops(shops: ShopItem[], filters: Partial<ShopFiltersData>): ShopItem[] {
|
||||
return Object.entries(filters).reduce(
|
||||
(acc: ShopItem[], [filterName, filterData]: [keyof ShopFiltersData, any]) => {
|
||||
(acc: ShopItem[], [filterName, filterData]: [keyof ShopFiltersData, string]) => {
|
||||
switch (filterName) {
|
||||
case 'query':
|
||||
return this.filterQuery(shops, filterData);
|
||||
|
@ -1,177 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { TranslocoTestingModule } from '@ngneat/transloco';
|
||||
import { Shop } from '@vality/swag-payments';
|
||||
import cloneDeep from 'lodash-es/cloneDeep';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { ShopsDataService } from '@dsh/app/shared';
|
||||
|
||||
import { ShopActionsComponent } from './shop-actions.component';
|
||||
import { generateMockShopItem } from '../../../../tests/generate-shop-item';
|
||||
import { ShopActionsService } from '../../services/shop-actions/shop-actions.service';
|
||||
import { ShopActionResult } from '../../types/shop-action-result';
|
||||
|
||||
class MockShopsService {}
|
||||
|
||||
@Injectable()
|
||||
class MockApiShopsService extends ShopsDataService {
|
||||
set mockShops(shops: Shop[]) {
|
||||
this._shops = shops;
|
||||
}
|
||||
private _shops: Shop[] = [];
|
||||
|
||||
set mockActionResponse(response: any) {
|
||||
this._actionResponse = response;
|
||||
}
|
||||
private _actionResponse: any;
|
||||
|
||||
reloadShops() {
|
||||
this._shops = cloneDeep(this._shops);
|
||||
}
|
||||
}
|
||||
|
||||
class MockMatDialogRef<T = any, R = any> extends MatDialogRef<T, R> {}
|
||||
|
||||
class MockMatDialog {
|
||||
private _dialogRef: MockMatDialogRef;
|
||||
|
||||
get dialogRef(): MockMatDialogRef {
|
||||
return this._dialogRef;
|
||||
}
|
||||
|
||||
open<T, R = any>(): MockMatDialogRef<T, R> {
|
||||
this._dialogRef = new MockMatDialogRef(null, null);
|
||||
return this._dialogRef;
|
||||
}
|
||||
}
|
||||
|
||||
describe('ShopActionsComponent', () => {
|
||||
let component: ShopActionsComponent;
|
||||
let fixture: ComponentFixture<ShopActionsComponent>;
|
||||
let mockDialog: MockMatDialog;
|
||||
let actionsService: ShopActionsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockDialog = new MockMatDialog();
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslocoTestingModule.withLangs({
|
||||
en: {
|
||||
shops: {
|
||||
panel: {
|
||||
activate: 'activate',
|
||||
suspend: 'suspend',
|
||||
},
|
||||
suspend: {
|
||||
success: 'success suspend',
|
||||
error: 'error suspend',
|
||||
},
|
||||
activate: {
|
||||
success: 'success activate',
|
||||
error: 'error activate',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
MatSnackBarModule,
|
||||
],
|
||||
declarations: [ShopActionsComponent],
|
||||
providers: [
|
||||
ShopActionsService,
|
||||
{
|
||||
provide: ShopsDataService,
|
||||
useClass: MockApiShopsService,
|
||||
},
|
||||
{
|
||||
provide: ShopsDataService,
|
||||
useClass: MockShopsService,
|
||||
},
|
||||
{
|
||||
provide: MatDialog,
|
||||
useValue: mockDialog,
|
||||
},
|
||||
],
|
||||
})
|
||||
.overrideComponent(ShopActionsComponent, { set: { providers: [] } })
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ShopActionsComponent);
|
||||
component = fixture.componentInstance;
|
||||
actionsService = TestBed.inject(ShopActionsService);
|
||||
|
||||
component.shop = generateMockShopItem(1);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('suspend', () => {
|
||||
it('should call service suspend method', () => {
|
||||
const spyOnSuspend = spyOn(actionsService, 'suspend').and.returnValue(of(ShopActionResult.Success));
|
||||
|
||||
component.suspend('id');
|
||||
|
||||
expect(spyOnSuspend).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnSuspend).toHaveBeenCalledWith('id');
|
||||
});
|
||||
|
||||
it('should emit update data if suspend was successful', () => {
|
||||
const spyOnSuspend = spyOn(actionsService, 'suspend').and.returnValue(of(ShopActionResult.Success));
|
||||
const spyOnUpdateData = spyOn(component.updateData, 'emit').and.callThrough();
|
||||
|
||||
component.suspend('id');
|
||||
|
||||
expect(spyOnSuspend).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnUpdateData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should emit update data if suspend was not successful', () => {
|
||||
const spyOnSuspend = spyOn(actionsService, 'suspend').and.returnValue(of(ShopActionResult.Error));
|
||||
const spyOnUpdateData = spyOn(component.updateData, 'emit').and.callThrough();
|
||||
|
||||
component.suspend('id');
|
||||
|
||||
expect(spyOnSuspend).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnUpdateData).not.toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('activate', () => {
|
||||
it('should call service activate method', () => {
|
||||
const spyOnActivate = spyOn(actionsService, 'activate').and.returnValue(of(ShopActionResult.Success));
|
||||
|
||||
component.activate('id');
|
||||
|
||||
expect(spyOnActivate).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnActivate).toHaveBeenCalledWith('id');
|
||||
});
|
||||
|
||||
it('should emit update data if activate was successful', () => {
|
||||
const spyOnActivate = spyOn(actionsService, 'activate').and.returnValue(of(ShopActionResult.Success));
|
||||
const spyOnUpdateData = spyOn(component.updateData, 'emit').and.callThrough();
|
||||
|
||||
component.activate('id');
|
||||
|
||||
expect(spyOnActivate).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnUpdateData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should emit update data if activate was not successful', () => {
|
||||
const spyOnActivate = spyOn(actionsService, 'activate').and.returnValue(of(ShopActionResult.Error));
|
||||
const spyOnUpdateData = spyOn(component.updateData, 'emit').and.callThrough();
|
||||
|
||||
component.activate('id');
|
||||
|
||||
expect(spyOnActivate).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnUpdateData).not.toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
@ -5,7 +5,6 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import isEmpty from 'lodash-es/isEmpty';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import negate from 'lodash-es/negate';
|
||||
// eslint-disable-next-line you-dont-need-lodash-underscore/omit
|
||||
import omit from 'lodash-es/omit';
|
||||
import pick from 'lodash-es/pick';
|
||||
import { combineLatest, defer, ReplaySubject } from 'rxjs';
|
||||
|
@ -23,9 +23,9 @@ export class WebhookEventsComponent {
|
||||
get events(): WithdrawalsTopic.EventTypesEnum[] | DestinationsTopic.EventTypesEnum[] {
|
||||
switch (this.scope.topic) {
|
||||
case 'WithdrawalsTopic':
|
||||
return (this.scope as any as WithdrawalsTopic).eventTypes;
|
||||
return (this.scope as unknown as WithdrawalsTopic).eventTypes;
|
||||
case 'DestinationsTopic':
|
||||
return (this.scope as any as DestinationsTopic).eventTypes;
|
||||
return (this.scope as unknown as DestinationsTopic).eventTypes;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import isEmpty from 'lodash-es/isEmpty';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import negate from 'lodash-es/negate';
|
||||
// eslint-disable-next-line you-dont-need-lodash-underscore/omit
|
||||
import omit from 'lodash-es/omit';
|
||||
import pick from 'lodash-es/pick';
|
||||
import { combineLatest, defer, ReplaySubject } from 'rxjs';
|
||||
|
@ -68,7 +68,7 @@ export class MaxLengthInputComponent implements OnChanges, ControlValueAccessor
|
||||
this.innerOnTouched();
|
||||
}
|
||||
|
||||
registerOnChange(onChange: (value: any) => void): void {
|
||||
registerOnChange(onChange: (value: unknown) => void): void {
|
||||
this.formControl.valueChanges.pipe(skip(1), untilDestroyed(this)).subscribe((value: string) => {
|
||||
onChange(value);
|
||||
});
|
||||
|
@ -7,5 +7,5 @@ export class ExpandableRadioGroupItemDirective {
|
||||
@Input()
|
||||
dshExpandableRadioGroupItem: string | number;
|
||||
|
||||
constructor(public readonly templateRef: TemplateRef<any>) {}
|
||||
constructor(public readonly templateRef: TemplateRef<ExpandableRadioGroupItemDirective>) {}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { FormControl } from '@ngneat/reactive-forms';
|
||||
|
||||
import { getTextContent } from '@dsh/app/shared/tests/get-text-content';
|
||||
import { getTranslocoModule } from '@dsh/app/shared/tests/get-transloco-module';
|
||||
import { createArrayOfLength } from '@dsh/app/shared/utils';
|
||||
|
||||
import { ExpandableRadioGroupItemDirective } from './directives/expandable-radio-group-item/expandable-radio-group-item.directive';
|
||||
@ -20,11 +19,10 @@ describe('ExpandableRadioGroupComponent', () => {
|
||||
let component: ExpandableRadioGroupComponent;
|
||||
let fixture: ComponentFixture<ExpandableRadioGroupComponent>;
|
||||
|
||||
async function createComponent(components: any[] = []) {
|
||||
async function createComponent(components: unknown[] = []) {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
getTranslocoModule(),
|
||||
MatRadioModule,
|
||||
InlineShowAllToggleModule,
|
||||
ReactiveFormsModule,
|
||||
|
@ -20,8 +20,8 @@ export type TypeUnion<C, E> = {
|
||||
export function createTypeUnionDefaultForm<C, E>(): FormGroup<TypeUnion<C, E>> {
|
||||
return new FormGroup<TypeUnion<C, E>>({
|
||||
type: new FormControl<Type>(null),
|
||||
created: new FormControl<C>({ value: null, disabled: true }) as any,
|
||||
existing: new FormControl<E>({ value: null, disabled: true }) as any,
|
||||
created: new FormControl<C>({ value: null, disabled: true }) as never,
|
||||
existing: new FormControl<E>({ value: null, disabled: true }) as never,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
*/
|
||||
export class NoContentDirective implements AfterContentChecked {
|
||||
@Input()
|
||||
dshNoContent: TemplateRef<any>;
|
||||
dshNoContent: TemplateRef<unknown>;
|
||||
|
||||
private get element(): HTMLElement {
|
||||
return this.elementRef.nativeElement;
|
||||
|
@ -3,9 +3,9 @@ import { Type } from '@angular/core';
|
||||
import { CustomError } from '@dsh/app/shared/services/error/models/custom-error';
|
||||
|
||||
export class ComponentInputError extends CustomError {
|
||||
readonly classRef: Type<any>;
|
||||
readonly classRef: Type<unknown>;
|
||||
|
||||
constructor(message: string, classRef: Type<any>) {
|
||||
constructor(message: string, classRef: Type<unknown>) {
|
||||
super(message);
|
||||
this.classRef = classRef;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export interface FetchAction<P = any> {
|
||||
export interface FetchAction<P = void> {
|
||||
type: 'search' | 'fetchMore';
|
||||
value?: P;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export interface FetchResult<T> {
|
||||
result?: T[];
|
||||
continuationToken?: string;
|
||||
error?: any;
|
||||
error?: unknown;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { TestScheduler } from 'rxjs/testing';
|
||||
import { FetchResult } from './fetch-result';
|
||||
import { PartialFetcher } from './partial-fetcher';
|
||||
|
||||
function assertDeepEqual(actual: any, expected: any) {
|
||||
function assertDeepEqual(actual: unknown, expected: unknown) {
|
||||
expect(actual).toEqual(expected);
|
||||
}
|
||||
|
||||
@ -13,19 +13,22 @@ function createScheduler() {
|
||||
}
|
||||
|
||||
describe('PartialFetch', () => {
|
||||
class PartialFetched extends PartialFetcher<any, any> {
|
||||
constructor(private fetchFn: (params?: any, continuationToken?: string) => Observable<any>, debounce?: number) {
|
||||
class PartialFetched extends PartialFetcher<unknown, unknown> {
|
||||
constructor(
|
||||
private fetchFn: (params?: unknown, continuationToken?: string) => Observable<unknown>,
|
||||
debounce?: number
|
||||
) {
|
||||
super(debounce);
|
||||
}
|
||||
|
||||
protected fetch(params: any, continuationToken: string) {
|
||||
protected fetch(params: unknown, continuationToken: string) {
|
||||
return this.fetchFn(params, continuationToken);
|
||||
}
|
||||
}
|
||||
|
||||
it('should init', () => {
|
||||
createScheduler().run(({ cold, expectObservable }) => {
|
||||
const result: FetchResult<any> = { result: ['test'] };
|
||||
const result: FetchResult<unknown> = { result: ['test'] };
|
||||
const partialFetched = new PartialFetched(() => cold('--x|', { x: result }), 100);
|
||||
expectObservable(partialFetched.searchResult$).toBe('');
|
||||
expectObservable(partialFetched.errors$).toBe('');
|
||||
@ -36,7 +39,7 @@ describe('PartialFetch', () => {
|
||||
|
||||
it('should search with debounce', () => {
|
||||
createScheduler().run(({ cold, expectObservable }) => {
|
||||
const result: FetchResult<any> = { result: ['test'] };
|
||||
const result: FetchResult<unknown> = { result: ['test'] };
|
||||
const partialFetched = new PartialFetched(() => cold('--x|', { x: result }), 100);
|
||||
partialFetched.search(null);
|
||||
expectObservable(partialFetched.searchResult$).toBe('100ms --0', [['test']]);
|
||||
@ -52,7 +55,7 @@ describe('PartialFetch', () => {
|
||||
(_params, token) =>
|
||||
cold('--x|', {
|
||||
x: { result: [token], continuationToken: token ? token + '0' : 'token' },
|
||||
} as FetchResult<any>),
|
||||
}),
|
||||
0
|
||||
);
|
||||
partialFetched.search('token');
|
||||
@ -74,7 +77,8 @@ describe('PartialFetch', () => {
|
||||
it('should reload with old params', () => {
|
||||
createScheduler().run(({ cold, expectObservable }) => {
|
||||
const partialFetched = new PartialFetched(
|
||||
(params) => cold('--x|', { x: { result: [params], continuationToken: 'token' } as FetchResult<any> }),
|
||||
(params) =>
|
||||
cold('--x|', { x: { result: [params], continuationToken: 'token' } as FetchResult<unknown> }),
|
||||
0
|
||||
);
|
||||
partialFetched.search('params');
|
||||
|
@ -31,7 +31,7 @@ export abstract class PartialFetcher<R, P> {
|
||||
readonly hasMore$: Observable<boolean>;
|
||||
readonly doAction$: Observable<boolean>;
|
||||
readonly doSearchAction$: Observable<boolean>;
|
||||
readonly errors$: Observable<any>;
|
||||
readonly errors$: Observable<unknown>;
|
||||
|
||||
private action$ = new Subject<FetchAction<P>>();
|
||||
|
||||
|
@ -45,7 +45,7 @@ describe('ShopContractDetailsService', () => {
|
||||
status: Contract.StatusEnum.Active,
|
||||
contractor: {
|
||||
contractorType: 'LegalEntity',
|
||||
} as any,
|
||||
} as never,
|
||||
paymentInstitutionID: 2,
|
||||
});
|
||||
});
|
||||
|
@ -44,7 +44,9 @@ export class ShopContractDetailsService {
|
||||
filter((result) => result !== 'error'),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe((contract) => this.contract$.next(contract as any));
|
||||
.subscribe((contract) =>
|
||||
this.contract$.next(contract as unknown as Overwrite<Contract, { contractor: RussianLegalEntity }>)
|
||||
);
|
||||
}
|
||||
|
||||
requestContract(contractID: string): void {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { instance, mock } from 'ts-mockito';
|
||||
|
||||
export function provideMockService<T extends new (...args: any) => any>(
|
||||
export function provideMockService<T extends new (...args: unknown[]) => unknown>(
|
||||
service: T,
|
||||
mockedService?: InstanceType<T>
|
||||
): {
|
||||
|
@ -1,3 +1,3 @@
|
||||
export interface Initializable {
|
||||
init(...args: any[]): void;
|
||||
init(...args: unknown[]): void;
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
export function createArrayOfLength(length: number, fillValue: any = null): (any | null)[] {
|
||||
export function createArrayOfLength(length: number, fillValue: unknown = null): (unknown | null)[] {
|
||||
return new Array(length).fill(fillValue);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { AbstractControl, FormArray, FormGroup } from '@ngneat/reactive-forms';
|
||||
import { AbstractControl, FormGroup } from '@ngneat/reactive-forms';
|
||||
import isEmpty from 'lodash-es/isEmpty';
|
||||
import isNil from 'lodash-es/isNil';
|
||||
|
||||
@ -14,11 +14,3 @@ export function getAbstractControl<Control extends AbstractControl, GroupType =
|
||||
}
|
||||
return form.get(path) as Control;
|
||||
}
|
||||
|
||||
export function getTypedFormArray<T extends any[]>(control: AbstractControl<T>): FormArray<T[number]> {
|
||||
return control as any;
|
||||
}
|
||||
|
||||
export function getTypedFormGroup<T>(control: AbstractControl<T>): FormGroup<T> {
|
||||
return control as any;
|
||||
}
|
||||
|
@ -1,68 +0,0 @@
|
||||
@use '@angular/material' as mat;
|
||||
@import '../../../styles/utils/fill';
|
||||
|
||||
@mixin dsh-button-toggle-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
$divider-color: mat.get-color-from-palette($foreground, divider, 0.12);
|
||||
|
||||
.dsh-button-toggle {
|
||||
background: mat.get-color-from-palette($background, card);
|
||||
@include fill(mat.get-color-from-palette($foreground, text));
|
||||
|
||||
&-group .dsh-button-toggle + .dsh-button-toggle {
|
||||
border-left: solid 1px $divider-color;
|
||||
}
|
||||
|
||||
[dir='rtl'] .dsh-button-toggle-group .dsh-button-toggle + .dsh-button-toggle {
|
||||
border-left: none;
|
||||
border-right: solid 1px $divider-color;
|
||||
}
|
||||
|
||||
&-group.dsh-button-toggle-vertical {
|
||||
.dsh-button-toggle + .dsh-button-toggle {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: solid 1px $divider-color;
|
||||
}
|
||||
}
|
||||
|
||||
&-checked {
|
||||
&.dsh-button-toggle {
|
||||
@include fill(mat.get-color-from-palette($primary, default));
|
||||
}
|
||||
}
|
||||
|
||||
&-disabled {
|
||||
background-color: mat.get-color-from-palette($background, disabled-button-toggle);
|
||||
@include fill(mat.get-color-from-palette($foreground, disabled-button));
|
||||
|
||||
&.dsh-button-toggle {
|
||||
background: mat.get-color-from-palette($background, card);
|
||||
}
|
||||
|
||||
&.dsh-button-toggle-checked {
|
||||
background-color: mat.get-color-from-palette($background, selected-disabled-button);
|
||||
}
|
||||
}
|
||||
|
||||
&-standalone.dsh-button-toggle,
|
||||
&-group {
|
||||
border: solid 1px $divider-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin dsh-button-toggle-typography($config) {
|
||||
.dsh {
|
||||
&-button-toggle-standalone,
|
||||
&-button-toggle-group {
|
||||
font: {
|
||||
family: mat.font-family($config, button);
|
||||
size: mat.font-size($config, button);
|
||||
weight: mat.font-weight($config, button);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,209 +0,0 @@
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { FormsModule, NgModel, ReactiveFormsModule } from '@angular/forms';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { ButtonToggleComponent, ButtonToggleGroupDirective } from './button-toggle.component';
|
||||
import {
|
||||
ButtonToggleGroupWithFormControlComponent,
|
||||
ButtonToggleGroupWithNgModelComponent,
|
||||
} from './button-toggle.components.spec';
|
||||
import { ButtonToggleModule } from './button-toggle.module';
|
||||
|
||||
describe('DshButtonToggle with forms', () => {
|
||||
beforeEach(fakeAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ButtonToggleModule, FormsModule, ReactiveFormsModule],
|
||||
declarations: [ButtonToggleGroupWithNgModelComponent, ButtonToggleGroupWithFormControlComponent],
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
describe('using FormControl', () => {
|
||||
let fixture: ComponentFixture<ButtonToggleGroupWithFormControlComponent>;
|
||||
let groupDebugElement: DebugElement;
|
||||
let groupInstance: ButtonToggleGroupDirective;
|
||||
let testComponent: ButtonToggleGroupWithFormControlComponent;
|
||||
|
||||
beforeEach(fakeAsync(() => {
|
||||
fixture = TestBed.createComponent(ButtonToggleGroupWithFormControlComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
testComponent = fixture.debugElement.componentInstance;
|
||||
|
||||
groupDebugElement = fixture.debugElement.query(By.directive(ButtonToggleGroupDirective));
|
||||
groupInstance = groupDebugElement.injector.get<ButtonToggleGroupDirective>(ButtonToggleGroupDirective);
|
||||
}));
|
||||
|
||||
it('should toggle the disabled state', () => {
|
||||
testComponent.control.disable();
|
||||
|
||||
expect(groupInstance.disabled).toBe(true);
|
||||
|
||||
testComponent.control.enable();
|
||||
|
||||
expect(groupInstance.disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('should set the value', () => {
|
||||
testComponent.control.setValue('green');
|
||||
|
||||
expect(groupInstance.value).toBe('green');
|
||||
|
||||
testComponent.control.setValue('red');
|
||||
|
||||
expect(groupInstance.value).toBe('red');
|
||||
});
|
||||
|
||||
it('should register the on change callback', () => {
|
||||
const spy = jasmine.createSpy('onChange callback');
|
||||
|
||||
testComponent.control.registerOnChange(spy);
|
||||
testComponent.control.setValue('blue');
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('button toggle group with ngModel and change event', () => {
|
||||
let fixture: ComponentFixture<ButtonToggleGroupWithNgModelComponent>;
|
||||
let groupDebugElement: DebugElement;
|
||||
let buttonToggleDebugElements: DebugElement[];
|
||||
let groupInstance: ButtonToggleGroupDirective;
|
||||
let buttonToggleInstances: ButtonToggleComponent[];
|
||||
let testComponent: ButtonToggleGroupWithNgModelComponent;
|
||||
let groupNgModel: NgModel;
|
||||
let innerButtons: HTMLElement[];
|
||||
|
||||
beforeEach(fakeAsync(() => {
|
||||
fixture = TestBed.createComponent(ButtonToggleGroupWithNgModelComponent);
|
||||
fixture.detectChanges();
|
||||
testComponent = fixture.debugElement.componentInstance;
|
||||
|
||||
groupDebugElement = fixture.debugElement.query(By.directive(ButtonToggleGroupDirective));
|
||||
groupInstance = groupDebugElement.injector.get<ButtonToggleGroupDirective>(ButtonToggleGroupDirective);
|
||||
groupNgModel = groupDebugElement.injector.get<NgModel>(NgModel);
|
||||
|
||||
buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(ButtonToggleComponent));
|
||||
buttonToggleInstances = buttonToggleDebugElements.map((debugEl) => debugEl.componentInstance);
|
||||
innerButtons = buttonToggleDebugElements.map((debugEl) => debugEl.query(By.css('button')).nativeElement);
|
||||
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should update the model before firing change event', fakeAsync(() => {
|
||||
expect(testComponent.modelValue).toBeUndefined();
|
||||
expect(testComponent.lastEvent).toBeUndefined();
|
||||
|
||||
innerButtons[0].click();
|
||||
fixture.detectChanges();
|
||||
|
||||
tick();
|
||||
expect(testComponent.modelValue).toBe('red');
|
||||
expect(testComponent.lastEvent.value).toBe('red');
|
||||
}));
|
||||
|
||||
it('should set individual radio names based on the group name', () => {
|
||||
expect(groupInstance.name).toBeTruthy();
|
||||
for (const buttonToggle of buttonToggleInstances) {
|
||||
expect(buttonToggle.name).toBe(groupInstance.name);
|
||||
}
|
||||
|
||||
groupInstance.name = 'new name';
|
||||
for (const buttonToggle of buttonToggleInstances) {
|
||||
expect(buttonToggle.name).toBe(groupInstance.name);
|
||||
}
|
||||
});
|
||||
|
||||
it('should update the name of radio DOM elements if the name of the group changes', () => {
|
||||
expect(innerButtons.every((button) => button.getAttribute('name') === groupInstance.name)).toBe(
|
||||
true,
|
||||
'Expected all buttons to have the initial name.'
|
||||
);
|
||||
|
||||
fixture.componentInstance.groupName = 'changed-name';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupInstance.name).toBe('changed-name');
|
||||
expect(innerButtons.every((button) => button.getAttribute('name') === groupInstance.name)).toBe(
|
||||
true,
|
||||
'Expected all buttons to have the new name.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should check the corresponding button toggle on a group value change', () => {
|
||||
expect(groupInstance.value).toBeFalsy();
|
||||
for (const buttonToggle of buttonToggleInstances) {
|
||||
expect(buttonToggle.checked).toBeFalsy();
|
||||
}
|
||||
|
||||
groupInstance.value = 'red';
|
||||
for (const buttonToggle of buttonToggleInstances) {
|
||||
expect(buttonToggle.checked).toBe(groupInstance.value === buttonToggle.value);
|
||||
}
|
||||
|
||||
const selected = groupInstance.selected as ButtonToggleComponent;
|
||||
|
||||
expect(selected.value).toBe(groupInstance.value);
|
||||
});
|
||||
|
||||
it('should have the correct FormControl state initially and after interaction', fakeAsync(() => {
|
||||
expect(groupNgModel.valid).toBe(true);
|
||||
expect(groupNgModel.pristine).toBe(true);
|
||||
expect(groupNgModel.touched).toBe(false);
|
||||
|
||||
buttonToggleInstances[1].checked = true;
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
expect(groupNgModel.valid).toBe(true);
|
||||
expect(groupNgModel.pristine).toBe(true);
|
||||
expect(groupNgModel.touched).toBe(false);
|
||||
|
||||
innerButtons[2].click();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
expect(groupNgModel.valid).toBe(true);
|
||||
expect(groupNgModel.pristine).toBe(false);
|
||||
expect(groupNgModel.touched).toBe(true);
|
||||
}));
|
||||
|
||||
it('should update the ngModel value when selecting a button toggle', fakeAsync(() => {
|
||||
innerButtons[1].click();
|
||||
fixture.detectChanges();
|
||||
|
||||
tick();
|
||||
|
||||
expect(testComponent.modelValue).toBe('green');
|
||||
}));
|
||||
|
||||
it(
|
||||
'should maintain the selected value when swapping out the list of toggles with one ' +
|
||||
'that still contains the value',
|
||||
fakeAsync(() => {
|
||||
expect(buttonToggleInstances[0].checked).toBe(false);
|
||||
expect(fixture.componentInstance.modelValue).toBeFalsy();
|
||||
expect(groupInstance.value).toBeFalsy();
|
||||
|
||||
groupInstance.value = 'red';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(buttonToggleInstances[0].checked).toBe(true);
|
||||
expect(groupInstance.value).toBe('red');
|
||||
|
||||
fixture.componentInstance.options = [...fixture.componentInstance.options];
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
|
||||
buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(ButtonToggleComponent));
|
||||
buttonToggleInstances = buttonToggleDebugElements.map((debugEl) => debugEl.componentInstance);
|
||||
|
||||
expect(buttonToggleInstances[0].checked).toBe(true);
|
||||
expect(groupInstance.value).toBe('red');
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
@ -1,16 +0,0 @@
|
||||
<button
|
||||
#button
|
||||
class="dsh-button-toggle-button"
|
||||
type="button"
|
||||
[id]="buttonId"
|
||||
[attr.aria-pressed]="checked"
|
||||
[disabled]="disabled || null"
|
||||
[attr.name]="name || null"
|
||||
[attr.aria-label]="ariaLabel"
|
||||
[attr.aria-labelledby]="ariaLabelledby"
|
||||
(click)="_onButtonClick()"
|
||||
>
|
||||
<div class="dsh-button-toggle-label-content">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</button>
|
@ -1,105 +0,0 @@
|
||||
$mat-button-toggle-padding: 0 12px;
|
||||
$mat-button-toggle-height: 50px;
|
||||
$mat-button-toggle-border-radius: 4px;
|
||||
|
||||
.dsh {
|
||||
&-button-toggle {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:not(.dsh-button-toggle-disabled):hover .dsh-button-toggle-focus-overlay {
|
||||
opacity: 0.04;
|
||||
}
|
||||
|
||||
// On touch devices the hover state will linger on the element after the user has tapped.
|
||||
// Disable it, because it can be confused with focus. We target the :hover state explicitly,
|
||||
// because we still want to preserve the keyboard focus state for hybrid devices that have
|
||||
// a keyboard and a touchscreen.
|
||||
@media (hover: none) {
|
||||
&:not(.dsh-button-toggle-disabled):hover .dsh-button-toggle-focus-overlay {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-standalone,
|
||||
&-group {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
border-radius: $mat-button-toggle-border-radius;
|
||||
}
|
||||
|
||||
&-vertical {
|
||||
flex-direction: column;
|
||||
flex-basis: auto;
|
||||
flex-grow: 0;
|
||||
|
||||
&-label-content {
|
||||
// Vertical button toggles shouldn't be an inline-block, because the toggles should
|
||||
// fill the available width in the group.
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&-label-content {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
|
||||
.dsh-button-toggle & {
|
||||
line-height: $mat-button-toggle-height;
|
||||
padding: $mat-button-toggle-padding;
|
||||
}
|
||||
}
|
||||
|
||||
// Fixes SVG icons that get thrown off because of the `vertical-align` on the parent.
|
||||
.mat-mdc-icon svg {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&-label-content > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&-focus-overlay {
|
||||
border-radius: inherit;
|
||||
|
||||
// Disable pointer events to prevent it from hijacking user events.
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&-button {
|
||||
border: 0;
|
||||
background: none;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
outline: none;
|
||||
width: 100%; // Stretch the button in case the consumer set a custom width.
|
||||
cursor: pointer;
|
||||
|
||||
.dsh-button-toggle-disabled & {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
// Remove the extra focus outline that is added by Firefox on native buttons.
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,462 +0,0 @@
|
||||
import { FocusMonitor } from '@angular/cdk/a11y';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
import {
|
||||
AfterContentInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ContentChildren,
|
||||
Directive,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
forwardRef,
|
||||
HostBinding,
|
||||
HostListener,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Optional,
|
||||
Output,
|
||||
QueryList,
|
||||
ViewChild,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { MatButtonToggleGroup } from '@angular/material/button-toggle';
|
||||
import { mixinDisableRipple } from '@angular/material/core';
|
||||
|
||||
export type ToggleType = 'checkbox' | 'radio';
|
||||
|
||||
/**
|
||||
* Provider Expression that allows dsh-button-toggle-group to register as a ControlValueAccessor.
|
||||
* This allows it to support [(ngModel)].
|
||||
* @docs-private
|
||||
*/
|
||||
export const DSH_BUTTON_TOGGLE_GROUP_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => ButtonToggleGroupDirective),
|
||||
multi: true,
|
||||
};
|
||||
|
||||
let _uniqueIdCounter = 0;
|
||||
|
||||
export class DshButtonToggleChange {
|
||||
constructor(public source: ButtonToggleComponent, public value: any) {}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: 'dsh-button-toggle-group, [dshButtonToggleGroup]',
|
||||
providers: [
|
||||
DSH_BUTTON_TOGGLE_GROUP_VALUE_ACCESSOR,
|
||||
{ provide: MatButtonToggleGroup, useExisting: ButtonToggleGroupDirective },
|
||||
],
|
||||
exportAs: 'dshButtonToggleGroup',
|
||||
})
|
||||
export class ButtonToggleGroupDirective implements ControlValueAccessor, OnInit, AfterContentInit {
|
||||
/* Child button toggle buttons. */
|
||||
@ContentChildren(forwardRef(() => ButtonToggleComponent)) _buttonToggles: QueryList<ButtonToggleComponent>;
|
||||
|
||||
@HostBinding('class.dsh-button-toggle-group') groupClass = true;
|
||||
@HostBinding('attr.role') role = true;
|
||||
|
||||
// eslint-disable-next-line @angular-eslint/no-output-native
|
||||
@Output() readonly change: EventEmitter<DshButtonToggleChange> = new EventEmitter<DshButtonToggleChange>();
|
||||
@Output() readonly valueChange = new EventEmitter<any>();
|
||||
|
||||
private _vertical = false;
|
||||
private _multiple = false;
|
||||
private _disabled = false;
|
||||
private _selectionModel: SelectionModel<ButtonToggleComponent>;
|
||||
private _name = `dsh-button-toggle-group-${(_uniqueIdCounter += 1)}`;
|
||||
|
||||
private _rawValue: any;
|
||||
|
||||
/** `name` attribute for the underlying `input` element. */
|
||||
@Input()
|
||||
get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
set name(value: string) {
|
||||
this._name = value;
|
||||
|
||||
if (this._buttonToggles) {
|
||||
this._buttonToggles.forEach((toggle) => {
|
||||
toggle.name = this._name;
|
||||
toggle._markForCheck();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether the toggle group is vertical. */
|
||||
@HostBinding('class.dsh-button-toggle-vertical')
|
||||
@Input()
|
||||
get vertical(): boolean {
|
||||
return this._vertical;
|
||||
}
|
||||
set vertical(value: boolean) {
|
||||
this._vertical = coerceBooleanProperty(value);
|
||||
}
|
||||
|
||||
/** Value of the toggle group. */
|
||||
@Input()
|
||||
get value(): any {
|
||||
const selected = this._selectionModel ? this._selectionModel.selected : [];
|
||||
|
||||
if (this.multiple) {
|
||||
return selected.map((toggle) => toggle.value);
|
||||
}
|
||||
|
||||
return selected[0] ? selected[0].value : undefined;
|
||||
}
|
||||
set value(newValue: any) {
|
||||
this._setSelectionByValue(newValue);
|
||||
this.valueChange.emit(this.value);
|
||||
}
|
||||
|
||||
/** Selected button toggles in the group. */
|
||||
get selected() {
|
||||
const selected = this._selectionModel.selected;
|
||||
return this.multiple ? selected : selected[0] || null;
|
||||
}
|
||||
|
||||
/** Whether multiple button toggles can be selected. */
|
||||
@Input()
|
||||
get multiple(): boolean {
|
||||
return this._multiple;
|
||||
}
|
||||
set multiple(value: boolean) {
|
||||
this._multiple = coerceBooleanProperty(value);
|
||||
}
|
||||
|
||||
/** Whether multiple button toggle group is disabled. */
|
||||
@Input()
|
||||
get disabled(): boolean {
|
||||
return this._disabled;
|
||||
}
|
||||
set disabled(value: boolean) {
|
||||
this._disabled = coerceBooleanProperty(value);
|
||||
|
||||
if (this._buttonToggles) {
|
||||
this._buttonToggles.forEach((toggle) => toggle._markForCheck());
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private _changeDetector: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit() {
|
||||
this._selectionModel = new SelectionModel<ButtonToggleComponent>(this.multiple, undefined, false);
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this._selectionModel.select(...this._buttonToggles.filter((toggle) => toggle.checked));
|
||||
}
|
||||
|
||||
/**
|
||||
* The method to be called in order to update ngModel.
|
||||
* Now `ngModel` binding is not supported in multiple selection mode.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
_controlValueAccessorChangeFn: (value: any) => void = () => {};
|
||||
|
||||
/** onTouch function registered via registerOnTouch (ControlValueAccessor). */
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
_onTouched: () => any = () => {};
|
||||
|
||||
/**
|
||||
* Sets the model value. Implemented as part of ControlValueAccessor.
|
||||
* @param value Value to be set to the model.
|
||||
*/
|
||||
writeValue(value: any) {
|
||||
this.value = value;
|
||||
this._changeDetector.markForCheck();
|
||||
}
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
registerOnChange(fn: (value: any) => void) {
|
||||
this._controlValueAccessorChangeFn = fn;
|
||||
}
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
registerOnTouched(fn: any) {
|
||||
this._onTouched = fn;
|
||||
}
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
/** Dispatch change event with current selection and group value. */
|
||||
_emitChangeEvent(): void {
|
||||
const selected = this.selected;
|
||||
const source = Array.isArray(selected) ? selected[selected.length - 1] : selected;
|
||||
const event = new DshButtonToggleChange(source, this.value);
|
||||
this._controlValueAccessorChangeFn(event.value);
|
||||
this.change.emit(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs a button toggle's selected state with the model value.
|
||||
* @param toggle Toggle to be synced.
|
||||
* @param select Whether the toggle should be selected.
|
||||
* @param isUserInput Whether the change was a result of a user interaction.
|
||||
* @param deferEvents Whether to defer emitting the change events.
|
||||
*/
|
||||
_syncButtonToggle(toggle: ButtonToggleComponent, select: boolean, isUserInput = false, deferEvents = false) {
|
||||
// Deselect the currently-selected toggle, if we're in single-selection
|
||||
// mode and the button being toggled isn't selected at the moment.
|
||||
if (!this.multiple && this.selected && !toggle.checked) {
|
||||
(this.selected as ButtonToggleComponent).checked = false;
|
||||
}
|
||||
|
||||
if (select) {
|
||||
this._selectionModel.select(toggle);
|
||||
} else {
|
||||
this._selectionModel.deselect(toggle);
|
||||
}
|
||||
|
||||
// We need to defer in some cases in order to avoid "changed after checked errors", however
|
||||
// the side-effect is that we may end up updating the model value out of sequence in others
|
||||
// The `deferEvents` flag allows us to decide whether to do it on a case-by-case basis.
|
||||
if (deferEvents) {
|
||||
Promise.resolve(() => this._updateModelValue(isUserInput));
|
||||
} else {
|
||||
this._updateModelValue(isUserInput);
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks whether a button toggle is selected. */
|
||||
_isSelected(toggle: ButtonToggleComponent) {
|
||||
return this._selectionModel.isSelected(toggle);
|
||||
}
|
||||
|
||||
/** Determines whether a button toggle should be checked on init. */
|
||||
_isPrechecked(toggle: ButtonToggleComponent) {
|
||||
if (typeof this._rawValue === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.multiple && Array.isArray(this._rawValue)) {
|
||||
return this._rawValue.some((value) => toggle.value != null && value === toggle.value);
|
||||
}
|
||||
|
||||
return toggle.value === this._rawValue;
|
||||
}
|
||||
|
||||
/** Updates the selection state of the toggles in the group based on a value. */
|
||||
private _setSelectionByValue(value: any | any[]) {
|
||||
this._rawValue = value;
|
||||
|
||||
if (!this._buttonToggles) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.multiple && value) {
|
||||
if (!Array.isArray(value)) {
|
||||
throw Error('Value must be an array in multiple-selection mode.');
|
||||
}
|
||||
|
||||
this._clearSelection();
|
||||
value.forEach((currentValue: any) => this._selectValue(currentValue));
|
||||
} else {
|
||||
this._clearSelection();
|
||||
this._selectValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/** Clears the selected toggles. */
|
||||
private _clearSelection() {
|
||||
this._selectionModel.clear();
|
||||
this._buttonToggles.forEach((toggle) => (toggle.checked = false));
|
||||
}
|
||||
|
||||
/** Selects a value if there's a toggle that corresponds to it. */
|
||||
private _selectValue(value: any) {
|
||||
const correspondingOption = this._buttonToggles.find((toggle) => {
|
||||
return toggle.value != null && toggle.value === value;
|
||||
});
|
||||
|
||||
if (correspondingOption) {
|
||||
correspondingOption.checked = true;
|
||||
this._selectionModel.select(correspondingOption);
|
||||
}
|
||||
}
|
||||
|
||||
/** Syncs up the group's value with the model and emits the change event. */
|
||||
private _updateModelValue(isUserInput: boolean) {
|
||||
// Only emit the change event for user input.
|
||||
if (isUserInput) {
|
||||
this._emitChangeEvent();
|
||||
}
|
||||
|
||||
// Note: we emit this one no matter whether it was a user interaction, because
|
||||
// it is used by Angular to sync up the two-way data binding.
|
||||
this.valueChange.emit(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Boilerplate for applying mixins to the MatButtonToggle class.
|
||||
/** @docs-private */
|
||||
class MatButtonToggleBase {}
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const _MatButtonToggleMixinBase = mixinDisableRipple(MatButtonToggleBase);
|
||||
|
||||
/** Single button inside of a toggle group. */
|
||||
@Component({
|
||||
selector: 'dsh-button-toggle, [dshButtonToggle]',
|
||||
templateUrl: 'button-toggle.component.html',
|
||||
styleUrls: ['button-toggle.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
exportAs: 'dshButtonToggle',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ButtonToggleComponent extends _MatButtonToggleMixinBase implements OnInit, OnDestroy {
|
||||
private _isSingleSelector = false;
|
||||
private _checked = false;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
@Input('aria-label')
|
||||
ariaLabel: string;
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
@Input('aria-labelledby')
|
||||
ariaLabelledby: string = null;
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
@Input()
|
||||
value: any;
|
||||
|
||||
@HostBinding('class.dsh-button-toggle-checked')
|
||||
@Input()
|
||||
get checked(): boolean {
|
||||
return this.buttonToggleGroup ? this.buttonToggleGroup._isSelected(this) : this._checked;
|
||||
}
|
||||
set checked(value: boolean) {
|
||||
const newValue = coerceBooleanProperty(value);
|
||||
|
||||
if (newValue !== this._checked) {
|
||||
this._checked = newValue;
|
||||
|
||||
if (this.buttonToggleGroup) {
|
||||
this.buttonToggleGroup._syncButtonToggle(this, this._checked);
|
||||
}
|
||||
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
@HostBinding('attr.disabled')
|
||||
get attrDisabled() {
|
||||
return this.disabled ? 'disabled' : null;
|
||||
}
|
||||
|
||||
@HostBinding('class.dsh-button-toggle-disabled')
|
||||
@Input()
|
||||
get disabled(): boolean {
|
||||
return this._disabled || (this.buttonToggleGroup && this.buttonToggleGroup.disabled);
|
||||
}
|
||||
set disabled(value: boolean) {
|
||||
this._disabled = coerceBooleanProperty(value);
|
||||
}
|
||||
private _disabled = false;
|
||||
|
||||
@HostBinding('class.dsh-button-toggle-standalone')
|
||||
get isButtonToggleGroup() {
|
||||
return !this.buttonToggleGroup;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
@HostBinding('class.dsh-button-toggle')
|
||||
toggleClass = true;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
@HostBinding('attr.id')
|
||||
@Input()
|
||||
id: string;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
@HostBinding('attr.name')
|
||||
@Input()
|
||||
name: string;
|
||||
|
||||
/** Event emitted when the group value changes. */
|
||||
// eslint-disable-next-line @angular-eslint/no-output-native, @typescript-eslint/member-ordering
|
||||
@Output() readonly change: EventEmitter<DshButtonToggleChange> = new EventEmitter<DshButtonToggleChange>();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
@ViewChild('button', { static: true })
|
||||
_buttonElement: ElementRef<HTMLButtonElement>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
_type: ToggleType;
|
||||
/** The parent button toggle group (exclusive selection). Optional. */
|
||||
// eslint-disable-next-line @typescript-eslint/member-ordering
|
||||
buttonToggleGroup: ButtonToggleGroupDirective;
|
||||
|
||||
/** Unique ID for the underlying `button` element. */
|
||||
get buttonId(): string {
|
||||
return `${this.id}-button`;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@Optional() toggleGroup: ButtonToggleGroupDirective,
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _elementRef: ElementRef<HTMLElement>,
|
||||
private _focusMonitor: FocusMonitor
|
||||
) {
|
||||
super();
|
||||
|
||||
this.buttonToggleGroup = toggleGroup;
|
||||
}
|
||||
|
||||
@HostListener('focus')
|
||||
focus(): void {
|
||||
this._buttonElement.nativeElement.focus();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._isSingleSelector = this.buttonToggleGroup && !this.buttonToggleGroup.multiple;
|
||||
this._type = this._isSingleSelector ? 'radio' : 'checkbox';
|
||||
this.id = this.id || `dsh-button-toggle-${(_uniqueIdCounter += 1)}`;
|
||||
|
||||
if (this._isSingleSelector) {
|
||||
this.name = this.buttonToggleGroup.name;
|
||||
}
|
||||
|
||||
if (this.buttonToggleGroup && this.buttonToggleGroup._isPrechecked(this)) {
|
||||
this.checked = true;
|
||||
}
|
||||
|
||||
this._focusMonitor.monitor(this._elementRef, true);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
const group = this.buttonToggleGroup;
|
||||
|
||||
this._focusMonitor.stopMonitoring(this._elementRef);
|
||||
|
||||
// Remove the toggle from the selection once it's destroyed. Needs to happen
|
||||
// on the next tick in order to avoid "changed after checked" errors.
|
||||
if (group && group._isSelected(this)) {
|
||||
group._syncButtonToggle(this, false, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks the button toggle due to an interaction with the underlying native button. */
|
||||
_onButtonClick() {
|
||||
const newChecked = this._isSingleSelector ? true : !this._checked;
|
||||
|
||||
if (newChecked !== this._checked) {
|
||||
this._checked = newChecked;
|
||||
if (this.buttonToggleGroup) {
|
||||
this.buttonToggleGroup._syncButtonToggle(this, this._checked, true);
|
||||
this.buttonToggleGroup._onTouched();
|
||||
}
|
||||
}
|
||||
// Emit a change event when it's the single selector
|
||||
this.change.emit(new DshButtonToggleChange(this, this.value));
|
||||
}
|
||||
|
||||
_markForCheck() {
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
import { Component, QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { UntypedFormControl } from '@angular/forms';
|
||||
|
||||
import { ButtonToggleComponent, ButtonToggleGroupDirective, DshButtonToggleChange } from './button-toggle.component';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<dsh-button-toggle-group [disabled]="isGroupDisabled" [vertical]="isVertical" [(value)]="groupValue">
|
||||
<dsh-button-toggle value="test1" *ngIf="renderFirstToggle">Test1</dsh-button-toggle>
|
||||
<dsh-button-toggle value="test2">Test2</dsh-button-toggle>
|
||||
<dsh-button-toggle value="test3">Test3</dsh-button-toggle>
|
||||
</dsh-button-toggle-group>
|
||||
`,
|
||||
})
|
||||
export class ButtonTogglesInsideButtonToggleGroupComponent {
|
||||
isGroupDisabled = false;
|
||||
isVertical = false;
|
||||
groupValue: string;
|
||||
renderFirstToggle = true;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<dsh-button-toggle-group [name]="groupName" [(ngModel)]="modelValue" (change)="lastEvent = $event">
|
||||
<dsh-button-toggle *ngFor="let option of options" [value]="option.value">
|
||||
{{ option.label }}
|
||||
</dsh-button-toggle>
|
||||
</dsh-button-toggle-group>
|
||||
`,
|
||||
})
|
||||
export class ButtonToggleGroupWithNgModelComponent {
|
||||
groupName = 'group-name';
|
||||
modelValue: string;
|
||||
options = [
|
||||
{ label: 'Red', value: 'red' },
|
||||
{ label: 'Green', value: 'green' },
|
||||
{ label: 'Blue', value: 'blue' },
|
||||
];
|
||||
lastEvent: DshButtonToggleChange;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<dsh-button-toggle-group [disabled]="isGroupDisabled" [vertical]="isVertical" multiple>
|
||||
<dsh-button-toggle value="eggs">Eggs</dsh-button-toggle>
|
||||
<dsh-button-toggle value="flour">Flour</dsh-button-toggle>
|
||||
<dsh-button-toggle value="sugar">Sugar</dsh-button-toggle>
|
||||
</dsh-button-toggle-group>
|
||||
`,
|
||||
})
|
||||
export class ButtonTogglesInsideButtonToggleGroupMultipleComponent {
|
||||
isGroupDisabled = false;
|
||||
isVertical = false;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<dsh-button-toggle-group multiple [value]="value">
|
||||
<dsh-button-toggle [value]="0">Eggs</dsh-button-toggle>
|
||||
<dsh-button-toggle [value]="null">Flour</dsh-button-toggle>
|
||||
<dsh-button-toggle [value]="false">Sugar</dsh-button-toggle>
|
||||
<dsh-button-toggle>Sugar</dsh-button-toggle>
|
||||
</dsh-button-toggle-group>
|
||||
`,
|
||||
})
|
||||
export class FalsyButtonTogglesInsideButtonToggleGroupMultipleComponent {
|
||||
value: ('' | number | null | undefined | boolean)[] = [0];
|
||||
@ViewChildren(ButtonToggleComponent) toggles: QueryList<ButtonToggleComponent>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: ` <dsh-button-toggle>Yes</dsh-button-toggle> `,
|
||||
})
|
||||
export class StandaloneButtonToggleComponent {}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<dsh-button-toggle-group (change)="lastEvent = $event" value="red">
|
||||
<dsh-button-toggle value="red">Value Red</dsh-button-toggle>
|
||||
<dsh-button-toggle value="green">Value Green</dsh-button-toggle>
|
||||
</dsh-button-toggle-group>
|
||||
`,
|
||||
})
|
||||
export class ButtonToggleGroupWithInitialValueComponent {
|
||||
lastEvent: DshButtonToggleChange;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<dsh-button-toggle-group [formControl]="control">
|
||||
<dsh-button-toggle value="red">Value Red</dsh-button-toggle>
|
||||
<dsh-button-toggle value="green">Value Green</dsh-button-toggle>
|
||||
<dsh-button-toggle value="blue">Value Blue</dsh-button-toggle>
|
||||
</dsh-button-toggle-group>
|
||||
`,
|
||||
})
|
||||
export class ButtonToggleGroupWithFormControlComponent {
|
||||
control = new UntypedFormControl();
|
||||
}
|
||||
|
||||
/** Simple test component with an aria-label set. */
|
||||
@Component({
|
||||
template: ` <dsh-button-toggle aria-label="Super effective"></dsh-button-toggle> `,
|
||||
})
|
||||
export class ButtonToggleWithAriaLabelComponent {}
|
||||
|
||||
/** Simple test component with an aria-label set. */
|
||||
@Component({
|
||||
template: ` <dsh-button-toggle aria-labelledby="some-id"></dsh-button-toggle> `,
|
||||
})
|
||||
export class ButtonToggleWithAriaLabelledbyComponent {}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<dsh-button-toggle-group [(value)]="value">
|
||||
<dsh-button-toggle *ngFor="let toggle of possibleValues" [value]="toggle">
|
||||
{{ toggle }}
|
||||
</dsh-button-toggle>
|
||||
</dsh-button-toggle-group>
|
||||
`,
|
||||
})
|
||||
export class RepeatedButtonTogglesWithPreselectedValueComponent {
|
||||
@ViewChild(ButtonToggleGroupDirective) toggleGroup: ButtonToggleGroupDirective;
|
||||
@ViewChildren(ButtonToggleComponent) toggles: QueryList<ButtonToggleComponent>;
|
||||
|
||||
possibleValues = ['One', 'Two', 'Three'];
|
||||
value = 'Two';
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: ` <dsh-button-toggle name="custom-name"></dsh-button-toggle> `,
|
||||
})
|
||||
export class ButtonToggleWithStaticNameComponent {}
|
@ -1,13 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { ButtonToggleComponent, ButtonToggleGroupDirective } from './button-toggle.component';
|
||||
|
||||
const EXPORTED_DECLARATIONS = [ButtonToggleGroupDirective, ButtonToggleComponent];
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
exports: EXPORTED_DECLARATIONS,
|
||||
declarations: EXPORTED_DECLARATIONS,
|
||||
})
|
||||
export class ButtonToggleModule {}
|
@ -1,575 +0,0 @@
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import {
|
||||
ButtonToggleGroupWithInitialValueComponent,
|
||||
ButtonTogglesInsideButtonToggleGroupComponent,
|
||||
ButtonTogglesInsideButtonToggleGroupMultipleComponent,
|
||||
ButtonToggleWithAriaLabelComponent,
|
||||
ButtonToggleWithAriaLabelledbyComponent,
|
||||
ButtonToggleWithStaticNameComponent,
|
||||
FalsyButtonTogglesInsideButtonToggleGroupMultipleComponent,
|
||||
RepeatedButtonTogglesWithPreselectedValueComponent,
|
||||
StandaloneButtonToggleComponent,
|
||||
} from './button-toggle.components.spec';
|
||||
import { ButtonToggleComponent, ButtonToggleGroupDirective, ButtonToggleModule } from './index';
|
||||
|
||||
describe('DshButtonToggle without forms', () => {
|
||||
beforeEach(fakeAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ButtonToggleModule],
|
||||
declarations: [
|
||||
ButtonTogglesInsideButtonToggleGroupComponent,
|
||||
ButtonTogglesInsideButtonToggleGroupMultipleComponent,
|
||||
FalsyButtonTogglesInsideButtonToggleGroupMultipleComponent,
|
||||
ButtonToggleGroupWithInitialValueComponent,
|
||||
StandaloneButtonToggleComponent,
|
||||
ButtonToggleWithAriaLabelComponent,
|
||||
ButtonToggleWithAriaLabelledbyComponent,
|
||||
RepeatedButtonTogglesWithPreselectedValueComponent,
|
||||
ButtonToggleWithStaticNameComponent,
|
||||
],
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
describe('inside of an exclusive selection group', () => {
|
||||
let fixture: ComponentFixture<ButtonTogglesInsideButtonToggleGroupComponent>;
|
||||
let groupDebugElement: DebugElement;
|
||||
let groupNativeElement: HTMLElement;
|
||||
let buttonToggleDebugElements: DebugElement[];
|
||||
let buttonToggleNativeElements: HTMLElement[];
|
||||
let buttonToggleLabelElements: HTMLLabelElement[];
|
||||
let groupInstance: ButtonToggleGroupDirective;
|
||||
let buttonToggleInstances: ButtonToggleComponent[];
|
||||
let testComponent: ButtonTogglesInsideButtonToggleGroupComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ButtonTogglesInsideButtonToggleGroupComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
testComponent = fixture.debugElement.componentInstance;
|
||||
|
||||
groupDebugElement = fixture.debugElement.query(By.directive(ButtonToggleGroupDirective));
|
||||
groupNativeElement = groupDebugElement.nativeElement;
|
||||
groupInstance = groupDebugElement.injector.get<ButtonToggleGroupDirective>(ButtonToggleGroupDirective);
|
||||
|
||||
buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(ButtonToggleComponent));
|
||||
|
||||
buttonToggleNativeElements = buttonToggleDebugElements.map((debugEl) => debugEl.nativeElement);
|
||||
|
||||
buttonToggleLabelElements = fixture.debugElement
|
||||
.queryAll(By.css('button'))
|
||||
.map((debugEl) => debugEl.nativeElement);
|
||||
|
||||
buttonToggleInstances = buttonToggleDebugElements.map((debugEl) => debugEl.componentInstance);
|
||||
});
|
||||
|
||||
it('should set individual button toggle names based on the group name', () => {
|
||||
expect(groupInstance.name).toBeTruthy();
|
||||
for (const buttonToggle of buttonToggleInstances) {
|
||||
expect(buttonToggle.name).toBe(groupInstance.name);
|
||||
}
|
||||
});
|
||||
|
||||
it('should disable click interactions when the group is disabled', () => {
|
||||
testComponent.isGroupDisabled = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
buttonToggleNativeElements[0].click();
|
||||
expect(buttonToggleInstances[0].checked).toBe(false);
|
||||
expect(buttonToggleInstances[0].disabled).toBe(true);
|
||||
|
||||
testComponent.isGroupDisabled = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(buttonToggleInstances[0].disabled).toBe(false);
|
||||
|
||||
buttonToggleLabelElements[0].click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(buttonToggleInstances[0].checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should disable the underlying button when the group is disabled', () => {
|
||||
const buttons = buttonToggleNativeElements.map((toggle) => toggle.querySelector('button'));
|
||||
|
||||
expect(buttons.every((input) => input.disabled)).toBe(false);
|
||||
|
||||
testComponent.isGroupDisabled = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(buttons.every((input) => input.disabled)).toBe(true);
|
||||
});
|
||||
|
||||
it('should update the group value when one of the toggles changes', () => {
|
||||
expect(groupInstance.value).toBeFalsy();
|
||||
buttonToggleLabelElements[0].click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupInstance.value).toBe('test1');
|
||||
expect(groupInstance.selected).toBe(buttonToggleInstances[0]);
|
||||
});
|
||||
|
||||
it('should propagate the value change back up via a two-way binding', () => {
|
||||
expect(groupInstance.value).toBeFalsy();
|
||||
buttonToggleLabelElements[0].click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupInstance.value).toBe('test1');
|
||||
expect(testComponent.groupValue).toBe('test1');
|
||||
});
|
||||
|
||||
it('should update the group and toggles when one of the button toggles is clicked', () => {
|
||||
expect(groupInstance.value).toBeFalsy();
|
||||
buttonToggleLabelElements[0].click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupInstance.value).toBe('test1');
|
||||
expect(groupInstance.selected).toBe(buttonToggleInstances[0]);
|
||||
expect(buttonToggleInstances[0].checked).toBe(true);
|
||||
expect(buttonToggleInstances[1].checked).toBe(false);
|
||||
|
||||
buttonToggleLabelElements[1].click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupInstance.value).toBe('test2');
|
||||
expect(groupInstance.selected).toBe(buttonToggleInstances[1]);
|
||||
expect(buttonToggleInstances[0].checked).toBe(false);
|
||||
expect(buttonToggleInstances[1].checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should check a button toggle upon interaction with underlying native radio button', () => {
|
||||
buttonToggleLabelElements[0].click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(buttonToggleInstances[0].checked).toBe(true);
|
||||
expect(groupInstance.value);
|
||||
});
|
||||
|
||||
it('should change the vertical state', () => {
|
||||
expect(groupNativeElement.classList).not.toContain('dsh-button-toggle-vertical');
|
||||
|
||||
groupInstance.vertical = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupNativeElement.classList).toContain('dsh-button-toggle-vertical');
|
||||
});
|
||||
|
||||
it('should emit a change event from button toggles', fakeAsync(() => {
|
||||
expect(buttonToggleInstances[0].checked).toBe(false);
|
||||
|
||||
const changeSpy = jasmine.createSpy('button-toggle change listener');
|
||||
buttonToggleInstances[0].change.subscribe(changeSpy);
|
||||
|
||||
buttonToggleLabelElements[0].click();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(changeSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
buttonToggleLabelElements[0].click();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
// Always emit change event when button toggle is clicked
|
||||
expect(changeSpy).toHaveBeenCalledTimes(2);
|
||||
}));
|
||||
|
||||
it('should emit a change event from the button toggle group', fakeAsync(() => {
|
||||
expect(groupInstance.value).toBeFalsy();
|
||||
|
||||
const changeSpy = jasmine.createSpy('button-toggle-group change listener');
|
||||
groupInstance.change.subscribe(changeSpy);
|
||||
|
||||
buttonToggleLabelElements[0].click();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(changeSpy).toHaveBeenCalled();
|
||||
|
||||
buttonToggleLabelElements[1].click();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(changeSpy).toHaveBeenCalledTimes(2);
|
||||
}));
|
||||
|
||||
it('should update the group and button toggles when updating the group value', () => {
|
||||
expect(groupInstance.value).toBeFalsy();
|
||||
|
||||
testComponent.groupValue = 'test1';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupInstance.value).toBe('test1');
|
||||
expect(groupInstance.selected).toBe(buttonToggleInstances[0]);
|
||||
expect(buttonToggleInstances[0].checked).toBe(true);
|
||||
expect(buttonToggleInstances[1].checked).toBe(false);
|
||||
|
||||
testComponent.groupValue = 'test2';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupInstance.value).toBe('test2');
|
||||
expect(groupInstance.selected).toBe(buttonToggleInstances[1]);
|
||||
expect(buttonToggleInstances[0].checked).toBe(false);
|
||||
expect(buttonToggleInstances[1].checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should deselect all of the checkboxes when the group value is cleared', () => {
|
||||
buttonToggleInstances[0].checked = true;
|
||||
|
||||
expect(groupInstance.value).toBeTruthy();
|
||||
|
||||
groupInstance.value = null;
|
||||
|
||||
expect(buttonToggleInstances.every((toggle) => !toggle.checked)).toBe(true);
|
||||
});
|
||||
|
||||
it('should update the model if a selected toggle is removed', fakeAsync(() => {
|
||||
expect(groupInstance.value).toBeFalsy();
|
||||
buttonToggleLabelElements[0].click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupInstance.value).toBe('test1');
|
||||
expect(groupInstance.selected).toBe(buttonToggleInstances[0]);
|
||||
|
||||
testComponent.renderFirstToggle = false;
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
expect(groupInstance.value).toBeFalsy();
|
||||
expect(groupInstance.selected).toBeFalsy();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('with initial value and change event', () => {
|
||||
it('should not fire an initial change event', () => {
|
||||
const fixture = TestBed.createComponent(ButtonToggleGroupWithInitialValueComponent);
|
||||
const testComponent = fixture.debugElement.componentInstance;
|
||||
const groupDebugElement = fixture.debugElement.query(By.directive(ButtonToggleGroupDirective));
|
||||
const groupInstance: ButtonToggleGroupDirective =
|
||||
groupDebugElement.injector.get<ButtonToggleGroupDirective>(ButtonToggleGroupDirective);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
// Note that we cast to a boolean, because the event has some circular references
|
||||
// which will crash the runner when Jasmine attempts to stringify them.
|
||||
expect(!!testComponent.lastEvent).toBe(false);
|
||||
expect(groupInstance.value).toBe('red');
|
||||
|
||||
groupInstance.value = 'green';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(!!testComponent.lastEvent).toBe(false);
|
||||
expect(groupInstance.value).toBe('green');
|
||||
});
|
||||
});
|
||||
|
||||
describe('inside of a multiple selection group', () => {
|
||||
let fixture: ComponentFixture<ButtonTogglesInsideButtonToggleGroupMultipleComponent>;
|
||||
let groupDebugElement: DebugElement;
|
||||
let groupNativeElement: HTMLElement;
|
||||
let buttonToggleDebugElements: DebugElement[];
|
||||
let buttonToggleNativeElements: HTMLElement[];
|
||||
let buttonToggleLabelElements: HTMLLabelElement[];
|
||||
let groupInstance: ButtonToggleGroupDirective;
|
||||
let buttonToggleInstances: ButtonToggleComponent[];
|
||||
let testComponent: ButtonTogglesInsideButtonToggleGroupMultipleComponent;
|
||||
|
||||
beforeEach(fakeAsync(() => {
|
||||
fixture = TestBed.createComponent(ButtonTogglesInsideButtonToggleGroupMultipleComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
testComponent = fixture.debugElement.componentInstance;
|
||||
|
||||
groupDebugElement = fixture.debugElement.query(By.directive(ButtonToggleGroupDirective));
|
||||
groupNativeElement = groupDebugElement.nativeElement;
|
||||
groupInstance = groupDebugElement.injector.get<ButtonToggleGroupDirective>(ButtonToggleGroupDirective);
|
||||
|
||||
buttonToggleDebugElements = fixture.debugElement.queryAll(By.directive(ButtonToggleComponent));
|
||||
buttonToggleNativeElements = buttonToggleDebugElements.map((debugEl) => debugEl.nativeElement);
|
||||
buttonToggleLabelElements = fixture.debugElement
|
||||
.queryAll(By.css('button'))
|
||||
.map((debugEl) => debugEl.nativeElement);
|
||||
buttonToggleInstances = buttonToggleDebugElements.map((debugEl) => debugEl.componentInstance);
|
||||
}));
|
||||
|
||||
it('should disable click interactions when the group is disabled', () => {
|
||||
testComponent.isGroupDisabled = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
buttonToggleNativeElements[0].click();
|
||||
expect(buttonToggleInstances[0].checked).toBe(false);
|
||||
});
|
||||
|
||||
it('should check a button toggle when clicked', () => {
|
||||
expect(buttonToggleInstances.every((buttonToggle) => !buttonToggle.checked)).toBe(true);
|
||||
|
||||
const nativeCheckboxLabel = buttonToggleDebugElements[0].query(By.css('button')).nativeElement;
|
||||
|
||||
nativeCheckboxLabel.click();
|
||||
|
||||
expect(groupInstance.value).toEqual(['eggs']);
|
||||
expect(buttonToggleInstances[0].checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow for multiple toggles to be selected', () => {
|
||||
buttonToggleInstances[0].checked = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupInstance.value).toEqual(['eggs']);
|
||||
expect(buttonToggleInstances[0].checked).toBe(true);
|
||||
|
||||
buttonToggleInstances[1].checked = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupInstance.value).toEqual(['eggs', 'flour']);
|
||||
expect(buttonToggleInstances[1].checked).toBe(true);
|
||||
expect(buttonToggleInstances[0].checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should check a button toggle upon interaction with underlying native checkbox', () => {
|
||||
const nativeCheckboxButton = buttonToggleDebugElements[0].query(By.css('button')).nativeElement;
|
||||
|
||||
nativeCheckboxButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupInstance.value).toEqual(['eggs']);
|
||||
expect(buttonToggleInstances[0].checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should change the vertical state', () => {
|
||||
expect(groupNativeElement.classList).not.toContain('dsh-button-toggle-vertical');
|
||||
|
||||
groupInstance.vertical = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(groupNativeElement.classList).toContain('dsh-button-toggle-vertical');
|
||||
});
|
||||
|
||||
it('should deselect a button toggle when selected twice', fakeAsync(() => {
|
||||
buttonToggleLabelElements[0].click();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
expect(buttonToggleInstances[0].checked).toBe(true);
|
||||
expect(groupInstance.value).toEqual(['eggs']);
|
||||
|
||||
buttonToggleLabelElements[0].click();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
expect(groupInstance.value).toEqual([]);
|
||||
expect(buttonToggleInstances[0].checked).toBe(false);
|
||||
}));
|
||||
|
||||
it('should emit a change event for state changes', fakeAsync(() => {
|
||||
expect(buttonToggleInstances[0].checked).toBe(false);
|
||||
|
||||
const changeSpy = jasmine.createSpy('button-toggle change listener');
|
||||
buttonToggleInstances[0].change.subscribe(changeSpy);
|
||||
|
||||
buttonToggleLabelElements[0].click();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(changeSpy).toHaveBeenCalled();
|
||||
expect(groupInstance.value).toEqual(['eggs']);
|
||||
|
||||
buttonToggleLabelElements[0].click();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(groupInstance.value).toEqual([]);
|
||||
|
||||
// The default browser behavior is to emit an event, when the value was set
|
||||
// to false. That's because the current input type is set to `checkbox` when
|
||||
// using the multiple mode.
|
||||
expect(changeSpy).toHaveBeenCalledTimes(2);
|
||||
}));
|
||||
|
||||
it('should throw when attempting to assign a non-array value', () => {
|
||||
expect(() => {
|
||||
groupInstance.value = 'not-an-array';
|
||||
}).toThrowError(/Value must be an array/);
|
||||
});
|
||||
|
||||
// it('should be able to query for the deprecated `DshButtonToggleGroupDirectiveMultiple`', () => {
|
||||
// expect(fixture.debugElement.query(By.directive(DshButtonToggleGroupMultiple))).toBeTruthy();
|
||||
// });
|
||||
});
|
||||
|
||||
describe('as standalone', () => {
|
||||
let fixture: ComponentFixture<StandaloneButtonToggleComponent>;
|
||||
let buttonToggleDebugElement: DebugElement;
|
||||
let buttonToggleNativeElement: HTMLElement;
|
||||
let buttonToggleLabelElement: HTMLLabelElement;
|
||||
let buttonToggleInstance: ButtonToggleComponent;
|
||||
let buttonToggleButtonElement: HTMLButtonElement;
|
||||
|
||||
beforeEach(fakeAsync(() => {
|
||||
fixture = TestBed.createComponent(StandaloneButtonToggleComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
buttonToggleDebugElement = fixture.debugElement.query(By.directive(ButtonToggleComponent));
|
||||
buttonToggleNativeElement = buttonToggleDebugElement.nativeElement;
|
||||
buttonToggleLabelElement = fixture.debugElement.query(
|
||||
By.css('.dsh-button-toggle-label-content')
|
||||
).nativeElement;
|
||||
buttonToggleInstance = buttonToggleDebugElement.componentInstance;
|
||||
buttonToggleButtonElement = buttonToggleNativeElement.querySelector('button');
|
||||
}));
|
||||
|
||||
it('should toggle when clicked', fakeAsync(() => {
|
||||
buttonToggleLabelElement.click();
|
||||
fixture.detectChanges();
|
||||
flush();
|
||||
|
||||
expect(buttonToggleInstance.checked).toBe(true);
|
||||
|
||||
buttonToggleLabelElement.click();
|
||||
fixture.detectChanges();
|
||||
flush();
|
||||
|
||||
expect(buttonToggleInstance.checked).toBe(false);
|
||||
}));
|
||||
|
||||
it('should emit a change event for state changes', fakeAsync(() => {
|
||||
expect(buttonToggleInstance.checked).toBe(false);
|
||||
|
||||
const changeSpy = jasmine.createSpy('button-toggle change listener');
|
||||
buttonToggleInstance.change.subscribe(changeSpy);
|
||||
|
||||
buttonToggleLabelElement.click();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
expect(changeSpy).toHaveBeenCalled();
|
||||
|
||||
buttonToggleLabelElement.click();
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
// The default browser behavior is to emit an event, when the value was set
|
||||
// to false. That's because the current input type is set to `checkbox`.
|
||||
expect(changeSpy).toHaveBeenCalledTimes(2);
|
||||
}));
|
||||
|
||||
it('should focus on underlying input element when focus() is called', () => {
|
||||
const nativeButton = buttonToggleDebugElement.query(By.css('button')).nativeElement;
|
||||
expect(document.activeElement).not.toBe(nativeButton);
|
||||
|
||||
buttonToggleInstance.focus();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(document.activeElement).toBe(nativeButton);
|
||||
});
|
||||
|
||||
it('should not assign a name to the underlying input if one is not passed in', () => {
|
||||
expect(buttonToggleButtonElement.getAttribute('name')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should have correct aria-pressed attribute', () => {
|
||||
expect(buttonToggleButtonElement.getAttribute('aria-pressed')).toBe('false');
|
||||
|
||||
buttonToggleLabelElement.click();
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(buttonToggleButtonElement.getAttribute('aria-pressed')).toBe('true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('aria-label handling ', () => {
|
||||
it('should not set the aria-label attribute if none is provided', () => {
|
||||
const fixture = TestBed.createComponent(StandaloneButtonToggleComponent);
|
||||
const checkboxDebugElement = fixture.debugElement.query(By.directive(ButtonToggleComponent));
|
||||
const checkboxNativeElement = checkboxDebugElement.nativeElement;
|
||||
const buttonElement = checkboxNativeElement.querySelector('button') as HTMLButtonElement;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(buttonElement.hasAttribute('aria-label')).toBe(false);
|
||||
});
|
||||
|
||||
it('should use the provided aria-label', () => {
|
||||
const fixture = TestBed.createComponent(ButtonToggleWithAriaLabelComponent);
|
||||
const checkboxDebugElement = fixture.debugElement.query(By.directive(ButtonToggleComponent));
|
||||
const checkboxNativeElement = checkboxDebugElement.nativeElement;
|
||||
const buttonElement = checkboxNativeElement.querySelector('button') as HTMLButtonElement;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(buttonElement.getAttribute('aria-label')).toBe('Super effective');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with provided aria-labelledby ', () => {
|
||||
let checkboxDebugElement: DebugElement;
|
||||
let checkboxNativeElement: HTMLElement;
|
||||
let buttonElement: HTMLButtonElement;
|
||||
|
||||
it('should use the provided aria-labelledby', () => {
|
||||
const fixture = TestBed.createComponent(ButtonToggleWithAriaLabelledbyComponent);
|
||||
checkboxDebugElement = fixture.debugElement.query(By.directive(ButtonToggleComponent));
|
||||
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
||||
buttonElement = checkboxNativeElement.querySelector('button');
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(buttonElement.getAttribute('aria-labelledby')).toBe('some-id');
|
||||
});
|
||||
|
||||
it('should not assign aria-labelledby if none is provided', () => {
|
||||
const fixture = TestBed.createComponent(StandaloneButtonToggleComponent);
|
||||
checkboxDebugElement = fixture.debugElement.query(By.directive(ButtonToggleComponent));
|
||||
checkboxNativeElement = checkboxDebugElement.nativeElement;
|
||||
buttonElement = checkboxNativeElement.querySelector('button');
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(buttonElement.getAttribute('aria-labelledby')).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw on init when toggles are repeated and there is an initial value', () => {
|
||||
const fixture = TestBed.createComponent(RepeatedButtonTogglesWithPreselectedValueComponent);
|
||||
|
||||
expect(() => fixture.detectChanges()).not.toThrow();
|
||||
expect(fixture.componentInstance.toggleGroup.value).toBe('Two');
|
||||
expect(fixture.componentInstance.toggles.toArray()[1].checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should not throw on init when toggles are repeated and there is an initial value', () => {
|
||||
const fixture = TestBed.createComponent(ButtonToggleWithStaticNameComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
const hostNode: HTMLElement = fixture.nativeElement.querySelector('.dsh-button-toggle');
|
||||
|
||||
expect(hostNode.hasAttribute('name')).toBe(true);
|
||||
expect(hostNode.querySelector('button').getAttribute('name')).toBe('custom-name');
|
||||
});
|
||||
|
||||
it('should maintain the selected state when the value and toggles are swapped out at ' + 'the same time', () => {
|
||||
const fixture = TestBed.createComponent(RepeatedButtonTogglesWithPreselectedValueComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.toggleGroup.value).toBe('Two');
|
||||
expect(fixture.componentInstance.toggles.toArray()[1].checked).toBe(true);
|
||||
|
||||
fixture.componentInstance.possibleValues = ['Five', 'Six', 'Seven'];
|
||||
fixture.componentInstance.value = 'Seven';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.toggleGroup.value).toBe('Seven');
|
||||
expect(fixture.componentInstance.toggles.toArray()[2].checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should select falsy button toggle value in multiple selection', () => {
|
||||
const fixture = TestBed.createComponent(FalsyButtonTogglesInsideButtonToggleGroupMultipleComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.toggles.toArray()[0].checked).toBe(true);
|
||||
expect(fixture.componentInstance.toggles.toArray()[1].checked).toBe(false);
|
||||
expect(fixture.componentInstance.toggles.toArray()[2].checked).toBe(false);
|
||||
|
||||
fixture.componentInstance.value = [0, false];
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.toggles.toArray()[0].checked).toBe(true);
|
||||
expect(fixture.componentInstance.toggles.toArray()[1].checked).toBe(false);
|
||||
expect(fixture.componentInstance.toggles.toArray()[2].checked).toBe(true);
|
||||
});
|
||||
});
|
@ -1,2 +0,0 @@
|
||||
export * from './button-toggle.component';
|
||||
export * from './button-toggle.module';
|
@ -1,2 +1 @@
|
||||
export * from './button';
|
||||
export * from './button-toggle';
|
||||
|
@ -25,5 +25,5 @@ export const customTooltip = ({ series, dataPointIndex, w }) => {
|
||||
`;
|
||||
};
|
||||
|
||||
const getValueX = (series: any[], index: number): string =>
|
||||
const getValueX = (series, index: number): string =>
|
||||
series.reduce((acc, curr) => (acc ? acc : curr.data.length ? curr.data[index].x : acc), null);
|
||||
|
@ -29,5 +29,6 @@ export class DonutChartComponent implements OnInit {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
updateDataPointSelection = (_: any, __: any, options?: any) => this.dataSelect.emit(options.dataPointIndex);
|
||||
updateDataPointSelection = (_: unknown, __: unknown, options?: { dataPointIndex?: number }) =>
|
||||
this.dataSelect.emit(options.dataPointIndex);
|
||||
}
|
||||
|
@ -14,13 +14,11 @@ import { CustomFormControl } from '../utils';
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export class FormatInputComponent extends CustomFormControl {
|
||||
export class FormatInputComponent extends CustomFormControl<unknown> {
|
||||
mask: TextMaskConfig;
|
||||
prefix = '';
|
||||
postfix = '';
|
||||
size: string = null;
|
||||
toInternalValue: (v: any) => any;
|
||||
toPublicValue: (v: any) => any;
|
||||
|
||||
private _format: Type;
|
||||
@Input()
|
||||
|
@ -32,7 +32,7 @@ import { INPUT_MIXIN_BASE } from './input-base';
|
||||
/**
|
||||
* @deprecated use s-libs
|
||||
*/
|
||||
export class CustomFormControl<I = any, P = I>
|
||||
export class CustomFormControl<I, P = I>
|
||||
extends INPUT_MIXIN_BASE
|
||||
implements AfterViewInit, ControlValueAccessor, MatFormFieldControl<I>, OnDestroy, DoCheck, OnChanges
|
||||
{
|
||||
@ -232,15 +232,15 @@ export class CustomFormControl<I = any, P = I>
|
||||
}
|
||||
|
||||
protected getDetails(value: P): string {
|
||||
return value as any;
|
||||
return value as string;
|
||||
}
|
||||
|
||||
protected toInternalValue(value: P): I {
|
||||
return value as any;
|
||||
return value as unknown as I;
|
||||
}
|
||||
|
||||
protected toPublicValue(value: I): P {
|
||||
return value as any;
|
||||
return value as unknown as P;
|
||||
}
|
||||
|
||||
private registerMonitors() {
|
||||
|
@ -6,5 +6,5 @@ import { ChangeDetectionStrategy, Component, TemplateRef, ViewChild } from '@ang
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AccordionItemContentComponent {
|
||||
@ViewChild(TemplateRef, { static: true }) templateRef: TemplateRef<any>;
|
||||
@ViewChild(TemplateRef, { static: true }) templateRef: TemplateRef<unknown>;
|
||||
}
|
||||
|
@ -4,5 +4,5 @@ import { Directive, TemplateRef } from '@angular/core';
|
||||
selector: 'ng-template[dshLazyPanelContent]',
|
||||
})
|
||||
export class LazyPanelContentDirective {
|
||||
constructor(public _template: TemplateRef<any>) {}
|
||||
constructor(public _template: TemplateRef<unknown>) {}
|
||||
}
|
||||
|
@ -42,8 +42,8 @@ export class DropdownComponent implements OnInit, OnDestroy {
|
||||
@Output() closed = new EventEmitter<void>();
|
||||
@Output() destroy = new EventEmitter<void>();
|
||||
|
||||
@ViewChild(TemplateRef, { static: true }) templateRef: TemplateRef<any>;
|
||||
@ContentChild(TemplateRef, { static: true }) contentTemplateRef: TemplateRef<any>;
|
||||
@ViewChild(TemplateRef, { static: true }) templateRef: TemplateRef<unknown>;
|
||||
@ContentChild(TemplateRef, { static: true }) contentTemplateRef: TemplateRef<unknown>;
|
||||
|
||||
state$ = new BehaviorSubject(State.Closed);
|
||||
triangleLeftOffset: string;
|
||||
|
@ -24,7 +24,7 @@ describe('DshDropdown', () => {
|
||||
function createComponent<T>(
|
||||
component: Type<T>,
|
||||
providers: Provider[] = [],
|
||||
declarations: any[] = []
|
||||
declarations: unknown[] = []
|
||||
): ComponentFixture<T> {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [DropdownModule, NoopAnimationsModule],
|
||||
|
@ -28,7 +28,7 @@ describe('DshStateNav', () => {
|
||||
function createComponent<T>(
|
||||
component: Type<T>,
|
||||
providers: Provider[] = [],
|
||||
declarations: any[] = []
|
||||
declarations: unknown[] = []
|
||||
): ComponentFixture<T> {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [StateNavModule],
|
||||
|
@ -9,7 +9,7 @@ import { ExpansionService } from '../../services/expansion/expansion.service';
|
||||
})
|
||||
export class NestedTableCollapseBodyDirective implements OnInit {
|
||||
constructor(
|
||||
private templateRef: TemplateRef<any>,
|
||||
private templateRef: TemplateRef<unknown>,
|
||||
private viewContainer: ViewContainerRef,
|
||||
private expansionService: ExpansionService
|
||||
) {}
|
||||
|
@ -1,5 +1,4 @@
|
||||
@import '../../components/buttons/button/button-theme';
|
||||
@import '../../components/buttons/button-toggle/button-toggle-theme';
|
||||
@import '../../components/layout/card/card-theme';
|
||||
@import '../../components/layout/panel/panel-theme';
|
||||
@import '../../components/layout/dropdown/dropdown-theme';
|
||||
@ -39,7 +38,6 @@
|
||||
@include dsh-navbar-item-theme($theme);
|
||||
@include dsh-mobile-grid-theme($theme);
|
||||
@include dsh-button-theme($theme);
|
||||
@include dsh-button-toggle-theme($theme);
|
||||
@include dsh-status-theme($theme);
|
||||
@include dsh-dadata-autocomplete-theme($theme);
|
||||
@include dsh-details-item-theme($theme);
|
||||
|
@ -1,7 +1,6 @@
|
||||
@import 'styles/dsh-base-typography';
|
||||
|
||||
@import '../../components/buttons/button/button-theme';
|
||||
@import '../../components/buttons/button-toggle/button-toggle-theme';
|
||||
@import '../../components/layout/card/card-theme';
|
||||
@import '../../components/layout/panel/panel-theme';
|
||||
@import '../../components/layout/dropdown/dropdown-theme';
|
||||
@ -30,7 +29,6 @@
|
||||
@include dsh-button-typography($config);
|
||||
@include dsh-state-nav-typography($config);
|
||||
@include dsh-card-typography($config);
|
||||
@include dsh-button-toggle-typography($config);
|
||||
@include dsh-dropdown-typography($config);
|
||||
@include dsh-dadata-autocomplete-typography($config);
|
||||
@include dsh-panel-typography($config);
|
||||
|
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export interface Dict<T = unknown> {
|
||||
[key: string]: T;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
export * from './mapping';
|
||||
export * from './map-tuple';
|
||||
export * from './component-changes';
|
||||
export * from './dict';
|
||||
export * from './at-least-one-of';
|
||||
|
@ -1,3 +0,0 @@
|
||||
export type MapTuple<P extends any[], T extends { [K in string | number | symbol]: number }> = {
|
||||
[K in keyof T]: P[T[K]];
|
||||
};
|
@ -2,7 +2,7 @@ import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({ name: 'activeClass' })
|
||||
export class ActiveClassPipe implements PipeTransform {
|
||||
transform(value: string, predicate: any): string {
|
||||
transform(value: string, predicate: unknown): string {
|
||||
return predicate ? ` ${value.trim()}` : '';
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
export function addDialogsClass(dialogs: MatDialogRef<any>[], className: string) {
|
||||
export function addDialogsClass(dialogs: MatDialogRef<unknown>[], className: string) {
|
||||
dialogs.forEach((d) => d.addPanelClass(className));
|
||||
return () => dialogs.forEach((d) => d.removePanelClass(className));
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
export function ignoreBeforeCompletion<P extends PropertyKey, C extends { [N in P]: (...args: any[]) => Subscription }>(
|
||||
target: C,
|
||||
propertyKey: P,
|
||||
descriptor: PropertyDescriptor
|
||||
) {
|
||||
export function ignoreBeforeCompletion<
|
||||
P extends PropertyKey,
|
||||
C extends { [N in P]: (...args: unknown[]) => Subscription }
|
||||
>(target: C, propertyKey: P, descriptor: PropertyDescriptor) {
|
||||
let lastSubscription: Subscription;
|
||||
const original = descriptor.value;
|
||||
descriptor.value = function (...args) {
|
||||
|
@ -6,7 +6,7 @@ import { Subject, Subscription } from 'rxjs';
|
||||
export function inProgressTo<T extends PropertyKey>(observableKey: T) {
|
||||
return function <
|
||||
P extends PropertyKey,
|
||||
C extends { [N in P]: (...args: any[]) => Subscription } & { [N in T]: Subject<boolean> }
|
||||
C extends { [N in P]: (...args: unknown[]) => Subscription } & { [N in T]: Subject<boolean> }
|
||||
>(target: C, propertyKey: P, descriptor: PropertyDescriptor) {
|
||||
const original = descriptor.value;
|
||||
let count = 0;
|
||||
|
@ -3,7 +3,7 @@ import { Dict } from '@dsh/type-utils';
|
||||
import { removeDictEmptyFields } from './remove-dict-empty-fields';
|
||||
|
||||
describe('removeDictEmptyFields', () => {
|
||||
let data: Dict<any>;
|
||||
let data: Dict;
|
||||
|
||||
it('should remove fields with empty strings', () => {
|
||||
data = {
|
||||
|
@ -3,6 +3,10 @@ import isNil from 'lodash-es/isNil';
|
||||
import isObject from 'lodash-es/isObject';
|
||||
|
||||
import { removeDictFields } from './remove-dict-fields';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export function removeDictEmptyFields<T>(dict: T): Partial<T> {
|
||||
return removeDictFields(dict, (value: unknown) => {
|
||||
return isObject(value) || typeof value === 'string' ? !isEmpty(value) : !isNil(value);
|
||||
|
@ -3,7 +3,7 @@ import { Dict } from '@dsh/type-utils';
|
||||
import { removeDictFields } from './remove-dict-fields';
|
||||
|
||||
describe('removeDictFields', () => {
|
||||
let data: Dict<any>;
|
||||
let data: Dict;
|
||||
|
||||
it('should remove fields if predicate returned false on its value', () => {
|
||||
data = {
|
||||
|
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export function removeDictFields<T>(dict: T, predicate: (value: unknown) => boolean): Partial<T> {
|
||||
return Object.entries(dict).reduce((acc: Partial<T>, [key, value]: [string, unknown]) => {
|
||||
if (predicate(value)) {
|
||||
|
@ -3,7 +3,7 @@ import { FormGroup, FormArray } from '@ngneat/reactive-forms';
|
||||
import { ControlsValue } from '@ngneat/reactive-forms/lib/types';
|
||||
|
||||
function hasControls<T>(control: AbstractControl): control is FormGroup<T> | FormArray<T> {
|
||||
return !!(control as any)?.controls;
|
||||
return !!(control as FormGroup<T> | FormArray<T>)?.controls;
|
||||
}
|
||||
|
||||
export function getValue<T extends AbstractControl>(control: T): T['value'] {
|
||||
@ -19,7 +19,7 @@ export function getValue<T extends AbstractControl>(control: T): T['value'] {
|
||||
}
|
||||
const result: Partial<ControlsValue<T>> = {};
|
||||
for (const [k, v] of Object.entries(control.controls)) {
|
||||
result[k] = getValue(v as any);
|
||||
result[k] = getValue(v as AbstractControl);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ import { AbstractControlOf } from '@ngneat/reactive-forms/lib/types';
|
||||
export function replaceFormArrayValue<T, C extends AbstractControlOf<T>>(
|
||||
formArray: FormArray<C>,
|
||||
value: T[],
|
||||
createControl: (v: T) => C
|
||||
createControl: (v: T) => AbstractControlOf<C>
|
||||
): FormArray<C> {
|
||||
formArray.clear();
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
formArray.push(createControl(item) as any);
|
||||
formArray.push(createControl(item));
|
||||
}
|
||||
}
|
||||
return formArray;
|
||||
|
@ -1,4 +1,6 @@
|
||||
export function queryParamsToStr(params: { [N in PropertyKey]: any }): string {
|
||||
import { Params } from '@angular/router';
|
||||
|
||||
export function queryParamsToStr(params: Params): string {
|
||||
return Object.entries(params)
|
||||
.map(([key, value]) => `${key}=${encodeURIComponent(String(value))}`)
|
||||
.join('&');
|
||||
|
10
tools/eslint-config/angular.js
vendored
10
tools/eslint-config/angular.js
vendored
@ -1,10 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
plugins: ['@angular-eslint', 'import', '@typescript-eslint'],
|
||||
extends: [
|
||||
'plugin:@angular-eslint/recommended',
|
||||
'plugin:@angular-eslint/template/process-inline-templates',
|
||||
require.resolve('./typescript.js'),
|
||||
],
|
||||
};
|
@ -1,12 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
plugins: ['import'],
|
||||
extends: ['plugin:import/errors', 'plugin:import/warnings', 'plugin:import/typescript'],
|
||||
rules: {
|
||||
'import/no-unresolved': 'off',
|
||||
'import/namespace': 'off',
|
||||
'import/no-cycle': 'error',
|
||||
...require('./rules').createImportOrderRule(),
|
||||
},
|
||||
};
|
@ -1,10 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
plugins: ['jasmine', '@typescript-eslint'],
|
||||
extends: ['plugin:jasmine/recommended'],
|
||||
rules: {
|
||||
'jasmine/new-line-before-expect': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'off',
|
||||
},
|
||||
};
|
@ -1,9 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
plugins: ['you-dont-need-lodash-underscore'],
|
||||
extends: ['plugin:you-dont-need-lodash-underscore/compatible'],
|
||||
rules: {
|
||||
'you-dont-need-lodash-underscore/is-nil': 'off',
|
||||
},
|
||||
};
|
@ -1,54 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* @param prefix app
|
||||
*/
|
||||
createAngularSelectorRules({ prefix } = {}) {
|
||||
return {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: prefix,
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: prefix,
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
/**
|
||||
* @param internalPathsPattern @app/**
|
||||
*/
|
||||
createImportOrderRule({ internalPathsPattern } = {}) {
|
||||
return {
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
groups: [['builtin', 'external'], 'internal', ['parent', 'sibling', 'index'], 'object'],
|
||||
pathGroups: internalPathsPattern
|
||||
? [
|
||||
{
|
||||
pattern: internalPathsPattern,
|
||||
group: 'internal',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
pathGroupsExcludedImportTypes: ['builtin'],
|
||||
'newlines-between': 'always',
|
||||
alphabetize: {
|
||||
order: 'asc',
|
||||
caseInsensitive: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
@ -1,97 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
plugins: ['@typescript-eslint'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
require.resolve('./import.js'),
|
||||
require.resolve('./unused-imports.js'),
|
||||
],
|
||||
rules: {
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: ['rxjs/Rx', 'rxjs/internal', 'lodash', 'lodash-es', '.'],
|
||||
patterns: ['src/*'],
|
||||
},
|
||||
],
|
||||
|
||||
'@typescript-eslint/no-unused-expressions': 'error',
|
||||
'@typescript-eslint/no-inferrable-types': 'off',
|
||||
'@typescript-eslint/member-ordering': [
|
||||
'error',
|
||||
{
|
||||
default: [
|
||||
'signature',
|
||||
|
||||
'public-field',
|
||||
'protected-field',
|
||||
'private-field',
|
||||
|
||||
'constructor',
|
||||
|
||||
'public-method',
|
||||
'protected-method',
|
||||
'private-method',
|
||||
],
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/naming-convention': [
|
||||
'error',
|
||||
{
|
||||
selector: 'default',
|
||||
// TODO: strictCamelCase
|
||||
format: ['camelCase'],
|
||||
leadingUnderscore: 'allow',
|
||||
},
|
||||
{
|
||||
selector: 'default',
|
||||
modifiers: ['destructured'],
|
||||
format: null,
|
||||
},
|
||||
{
|
||||
selector: 'typeLike',
|
||||
format: ['StrictPascalCase'],
|
||||
},
|
||||
{
|
||||
selector: 'variable',
|
||||
modifiers: ['const', 'global'],
|
||||
format: ['UPPER_CASE'],
|
||||
},
|
||||
{
|
||||
selector: 'variable',
|
||||
modifiers: ['const', 'global'],
|
||||
// Objects are functions too
|
||||
types: ['function'],
|
||||
format: ['UPPER_CASE', 'strictCamelCase'],
|
||||
},
|
||||
{
|
||||
selector: 'enumMember',
|
||||
format: ['StrictPascalCase'],
|
||||
},
|
||||
{
|
||||
selector: ['objectLiteralProperty', 'typeProperty'],
|
||||
format: ['camelCase', 'snake_case'],
|
||||
leadingUnderscore: 'allow',
|
||||
trailingUnderscore: 'allow',
|
||||
},
|
||||
{
|
||||
selector: [
|
||||
'classProperty',
|
||||
'objectLiteralProperty',
|
||||
'typeProperty',
|
||||
'classMethod',
|
||||
'objectLiteralMethod',
|
||||
'typeMethod',
|
||||
'accessor',
|
||||
'enumMember',
|
||||
],
|
||||
modifiers: ['requiresQuotes'],
|
||||
format: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user