1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-13 14:53:33 +00:00

Vault Strict Typing cleanup (#12512)

* remove strict types from `NewDeviceVerificationNotice`
- Add null default value for class properties
- Enforce that the userId is passed
- noticeState$ can return null

* remove strict types from `CopyCipherFieldService`
- refactor title to be a string rather than null

* remove strict types from `PasswordRepromptComponent`
- add guard to exit early on submit but also solves removing null/undefined from typing

* use bang to ensure required input

* remove strict types from `CopyCipherFieldDirective`
- add bang for required types
- add default values for null types

* add bang for constant variables in cipher form stories

* remove strict types from `DeleteAttachmentComponent`
- add bang for required types
- refactor title to be an empty string

* fix tests
This commit is contained in:
Nick Krantz
2025-01-02 15:37:48 -06:00
committed by GitHub
parent 15cc4ff1eb
commit cf9bc7c455
9 changed files with 25 additions and 33 deletions

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { importProvidersFrom } from "@angular/core"; import { importProvidersFrom } from "@angular/core";
import { action } from "@storybook/addon-actions"; import { action } from "@storybook/addon-actions";
import { import {
@@ -234,7 +232,7 @@ export const Edit: Story = {
config: { config: {
...defaultConfig, ...defaultConfig,
mode: "edit", mode: "edit",
originalCipher: defaultConfig.originalCipher, originalCipher: defaultConfig.originalCipher!,
}, },
}, },
}; };
@@ -245,7 +243,7 @@ export const PartialEdit: Story = {
config: { config: {
...defaultConfig, ...defaultConfig,
mode: "partial-edit", mode: "partial-edit",
originalCipher: defaultConfig.originalCipher, originalCipher: defaultConfig.originalCipher!,
}, },
}, },
}; };
@@ -256,7 +254,7 @@ export const Clone: Story = {
config: { config: {
...defaultConfig, ...defaultConfig,
mode: "clone", mode: "clone",
originalCipher: defaultConfig.originalCipher, originalCipher: defaultConfig.originalCipher!,
}, },
}, },
}; };
@@ -269,7 +267,7 @@ export const NoPersonalOwnership: Story = {
mode: "add", mode: "add",
allowPersonalOwnership: false, allowPersonalOwnership: false,
originalCipher: defaultConfig.originalCipher, originalCipher: defaultConfig.originalCipher,
organizations: defaultConfig.organizations, organizations: defaultConfig.organizations!,
}, },
}, },
}; };

View File

@@ -98,7 +98,7 @@ describe("DeleteAttachmentComponent", () => {
expect(showToast).toHaveBeenCalledWith({ expect(showToast).toHaveBeenCalledWith({
variant: "success", variant: "success",
title: null, title: "",
message: "deletedAttachment", message: "deletedAttachment",
}); });
}); });

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Component, EventEmitter, Input, Output } from "@angular/core";
@@ -24,10 +22,10 @@ import {
}) })
export class DeleteAttachmentComponent { export class DeleteAttachmentComponent {
/** Id of the cipher associated with the attachment */ /** Id of the cipher associated with the attachment */
@Input({ required: true }) cipherId: string; @Input({ required: true }) cipherId!: string;
/** The attachment that is can be deleted */ /** The attachment that is can be deleted */
@Input({ required: true }) attachment: AttachmentView; @Input({ required: true }) attachment!: AttachmentView;
/** Emits when the attachment is successfully deleted */ /** Emits when the attachment is successfully deleted */
@Output() onDeletionSuccess = new EventEmitter<void>(); @Output() onDeletionSuccess = new EventEmitter<void>();
@@ -56,7 +54,7 @@ export class DeleteAttachmentComponent {
this.toastService.showToast({ this.toastService.showToast({
variant: "success", variant: "success",
title: null, title: "",
message: this.i18nService.t("deletedAttachment"), message: this.i18nService.t("deletedAttachment"),
}); });

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, HostBinding, HostListener, Input, OnChanges, Optional } from "@angular/core"; import { Directive, HostBinding, HostListener, Input, OnChanges, Optional } from "@angular/core";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@@ -28,9 +26,9 @@ export class CopyCipherFieldDirective implements OnChanges {
alias: "appCopyField", alias: "appCopyField",
required: true, required: true,
}) })
action: Exclude<CopyAction, "hiddenField">; action!: Exclude<CopyAction, "hiddenField">;
@Input({ required: true }) cipher: CipherView; @Input({ required: true }) cipher!: CipherView;
constructor( constructor(
private copyCipherFieldService: CopyCipherFieldService, private copyCipherFieldService: CopyCipherFieldService,
@@ -52,7 +50,7 @@ export class CopyCipherFieldDirective implements OnChanges {
@HostListener("click") @HostListener("click")
async copy() { async copy() {
const value = this.getValueToCopy(); const value = this.getValueToCopy();
await this.copyCipherFieldService.copy(value, this.action, this.cipher); await this.copyCipherFieldService.copy(value ?? "", this.action, this.cipher);
} }
async ngOnChanges() { async ngOnChanges() {
@@ -69,7 +67,7 @@ export class CopyCipherFieldDirective implements OnChanges {
// If the directive is used on a menu item, update the menu item to prevent keyboard navigation // If the directive is used on a menu item, update the menu item to prevent keyboard navigation
if (this.menuItemDirective) { if (this.menuItemDirective) {
this.menuItemDirective.disabled = this.disabled; this.menuItemDirective.disabled = this.disabled ?? false;
} }
} }

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, ElementRef, HostBinding, Input, Renderer2 } from "@angular/core"; import { Directive, ElementRef, HostBinding, Input, Renderer2 } from "@angular/core";
import { ProductTierType } from "@bitwarden/common/billing/enums"; import { ProductTierType } from "@bitwarden/common/billing/enums";
@@ -11,7 +9,7 @@ export type OrgIconSize = "default" | "small" | "large";
selector: "[appOrgIcon]", selector: "[appOrgIcon]",
}) })
export class OrgIconDirective { export class OrgIconDirective {
@Input({ required: true }) tierType: ProductTierType; @Input({ required: true }) tierType!: ProductTierType;
@Input() size?: OrgIconSize = "default"; @Input() size?: OrgIconSize = "default";
constructor( constructor(

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { DialogRef } from "@angular/cdk/dialog"; import { DialogRef } from "@angular/cdk/dialog";
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
@@ -51,6 +49,12 @@ export class PasswordRepromptComponent {
) {} ) {}
submit = async () => { submit = async () => {
// Exit early when a master password is not provided.
// The form field required error will be shown to users in these cases.
if (!this.formGroup.value.masterPassword) {
return;
}
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)));
if (userId == null) { if (userId == null) {

View File

@@ -76,7 +76,7 @@ describe("CopyCipherFieldService", () => {
expect(toastService.showToast).toHaveBeenCalledWith({ expect(toastService.showToast).toHaveBeenCalledWith({
variant: "success", variant: "success",
message: "Username copied", message: "Username copied",
title: null, title: "",
}); });
expect(i18nService.t).toHaveBeenCalledWith("username"); expect(i18nService.t).toHaveBeenCalledWith("username");
expect(i18nService.t).toHaveBeenCalledWith("valueCopied", "Username"); expect(i18nService.t).toHaveBeenCalledWith("valueCopied", "Username");

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
@@ -131,7 +129,7 @@ export class CopyCipherFieldService {
this.toastService.showToast({ this.toastService.showToast({
variant: "success", variant: "success",
message: this.i18nService.t("valueCopied", this.i18nService.t(action.typeI18nKey)), message: this.i18nService.t("valueCopied", this.i18nService.t(action.typeI18nKey)),
title: null, title: "",
}); });
if (action.event !== undefined) { if (action.event !== undefined) {

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
@@ -17,8 +15,8 @@ import { UserId } from "@bitwarden/common/types/guid";
// If a user dismisses the notice, use "last_dismissal" to wait 7 days before re-prompting // If a user dismisses the notice, use "last_dismissal" to wait 7 days before re-prompting
// permanent_dismissal will be checked if the user should never see the notice again // permanent_dismissal will be checked if the user should never see the notice again
export class NewDeviceVerificationNotice { export class NewDeviceVerificationNotice {
last_dismissal: Date; last_dismissal: Date | null = null;
permanent_dismissal: boolean; permanent_dismissal: boolean | null = null;
constructor(obj: Partial<NewDeviceVerificationNotice>) { constructor(obj: Partial<NewDeviceVerificationNotice>) {
if (obj == null) { if (obj == null) {
@@ -52,12 +50,12 @@ export class NewDeviceVerificationNoticeService {
return this.stateProvider.getUser(userId, NEW_DEVICE_VERIFICATION_NOTICE_KEY); return this.stateProvider.getUser(userId, NEW_DEVICE_VERIFICATION_NOTICE_KEY);
} }
noticeState$(userId: UserId): Observable<NewDeviceVerificationNotice> { noticeState$(userId: UserId): Observable<NewDeviceVerificationNotice | null> {
return this.noticeState(userId).state$; return this.noticeState(userId).state$;
} }
async updateNewDeviceVerificationNoticeState( async updateNewDeviceVerificationNoticeState(
userId: UserId | null, userId: UserId,
newState: NewDeviceVerificationNotice, newState: NewDeviceVerificationNotice,
): Promise<void> { ): Promise<void> {
await this.noticeState(userId).update(() => { await this.noticeState(userId).update(() => {