TD-625: Move to @vality/eslint-config (#128)

This commit is contained in:
Rinat Arsaev 2023-06-23 17:29:26 +04:00 committed by GitHub
parent 6eff441919
commit 73de0048b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 3577 additions and 20024 deletions

View File

@ -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,
],
};

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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]>;
}
}

View File

@ -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);

View File

@ -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>;
}

View File

@ -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);
})
);
}

View File

@ -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();
});
});
});

View File

@ -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)));

View File

@ -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';

View File

@ -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()));

View File

@ -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),

View File

@ -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')
);

View File

@ -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,
};

View File

@ -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();
});
});
});

View File

@ -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();
});
});
});

View File

@ -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();
});
});
});

View File

@ -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),

View File

@ -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();
});
});
});

View File

@ -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();
});
});
});

View File

@ -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();
});
});
});

View File

@ -1,8 +1,8 @@
export interface Series {
name?: string;
data: {
x: any;
y: any;
x: unknown;
y: unknown;
fillColor?: string;
strokeColor?: string;
}[];

View File

@ -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>;

View File

@ -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;
}
}

View File

@ -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';

View File

@ -13,7 +13,7 @@ const toInvoiceTableData = (
amount,
currency,
status,
createdAt: createdAt as any,
createdAt,
invoiceID: id,
shopName: getShopNameById(s, shopID),
product,

View File

@ -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';

View File

@ -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();
});
});
});

View File

@ -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 {

View File

@ -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';

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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,

View File

@ -47,7 +47,7 @@ export class CreateReportDialogComponent implements OnInit {
);
}
create(formValue: any) {
create(formValue: unknown) {
this.createReportDialogService.create(formValue);
}

View File

@ -43,7 +43,7 @@ export class CreateReportDialogService {
});
}
create(formValue: any) {
create(formValue: unknown) {
this.create$.next(formValue);
}
}

View File

@ -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);

View File

@ -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);
});
});
});

View File

@ -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';

View File

@ -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;
}
}

View File

@ -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';

View File

@ -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);
});

View File

@ -7,5 +7,5 @@ export class ExpandableRadioGroupItemDirective {
@Input()
dshExpandableRadioGroupItem: string | number;
constructor(public readonly templateRef: TemplateRef<any>) {}
constructor(public readonly templateRef: TemplateRef<ExpandableRadioGroupItemDirective>) {}
}

View File

@ -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,

View File

@ -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,
});
}

View File

@ -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;

View File

@ -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;
}

View File

@ -1,4 +1,4 @@
export interface FetchAction<P = any> {
export interface FetchAction<P = void> {
type: 'search' | 'fetchMore';
value?: P;
}

View File

@ -1,5 +1,5 @@
export interface FetchResult<T> {
result?: T[];
continuationToken?: string;
error?: any;
error?: unknown;
}

View File

@ -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');

View File

@ -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>>();

View File

@ -45,7 +45,7 @@ describe('ShopContractDetailsService', () => {
status: Contract.StatusEnum.Active,
contractor: {
contractorType: 'LegalEntity',
} as any,
} as never,
paymentInstitutionID: 2,
});
});

View File

@ -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 {

View File

@ -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>
): {

View File

@ -1,3 +1,3 @@
export interface Initializable {
init(...args: any[]): void;
init(...args: unknown[]): void;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}
}
}
}

View File

@ -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');
})
);
});
});

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

@ -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();
}
}

View File

@ -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 {}

View File

@ -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 {}

View File

@ -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);
});
});

View File

@ -1,2 +0,0 @@
export * from './button-toggle.component';
export * from './button-toggle.module';

View File

@ -1,2 +1 @@
export * from './button';
export * from './button-toggle';

View File

@ -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);

View File

@ -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);
}

View File

@ -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()

View File

@ -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() {

View File

@ -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>;
}

View File

@ -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>) {}
}

View File

@ -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;

View File

@ -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],

View File

@ -28,7 +28,7 @@ describe('DshStateNav', () => {
function createComponent<T>(
component: Type<T>,
providers: Provider[] = [],
declarations: any[] = []
declarations: unknown[] = []
): ComponentFixture<T> {
TestBed.configureTestingModule({
imports: [StateNavModule],

View File

@ -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
) {}

View File

@ -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);

View File

@ -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);

View File

@ -1,3 +1,6 @@
/**
* @deprecated
*/
export interface Dict<T = unknown> {
[key: string]: T;
}

View File

@ -1,5 +1,4 @@
export * from './mapping';
export * from './map-tuple';
export * from './component-changes';
export * from './dict';
export * from './at-least-one-of';

View File

@ -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]];
};

View File

@ -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()}` : '';
}
}

View File

@ -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));
}

View File

@ -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) {

View File

@ -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;

View File

@ -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 = {

View File

@ -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);

View File

@ -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 = {

View File

@ -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)) {

View File

@ -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;
}

View File

@ -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;

View File

@ -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('&');

View File

@ -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'),
],
};

View File

@ -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(),
},
};

View File

@ -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',
},
};

View File

@ -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',
},
};

View File

@ -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,
},
},
],
};
},
};

View File

@ -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