-
Notifications
You must be signed in to change notification settings - Fork 1.5k
PM-22143 Refactor TS enums to be const objects (Send specific enums) #16399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6937282
a1b316f
4e68f24
6828f69
bb89cd9
8fff591
0e94644
30fd988
dae7d41
949f6a5
adbbf6a
83c2b92
a2e1081
948c6e3
9ef9c32
970bc9f
38dbe74
196dcbe
19f5e10
0577917
e1875a9
0517e28
19d4a02
356ffb1
a911b08
30b430a
9b8b8aa
c0255ba
2314d44
542e2c3
b34f4ea
4f2461d
6e30700
8265b7c
8641595
380fad1
b1b5f2f
8614dcf
4da1fa1
6d58a91
a4e4a70
c97b17a
687ca14
1d52c7a
624109e
e6c16b7
7fc47f2
a78c779
63c3049
5884b00
95f07c6
af4da3e
2993d93
ee785f2
57a7ba6
992f11f
4345d72
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,7 +37,7 @@ import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-heade | |
| import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; | ||
| import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popup-router-cache.service"; | ||
|
|
||
| import { SendV2Component, SendState } from "./send-v2.component"; | ||
| import { SendV2Component } from "./send-v2.component"; | ||
|
|
||
| describe("SendV2Component", () => { | ||
| let component: SendV2Component; | ||
|
|
@@ -142,12 +142,12 @@ describe("SendV2Component", () => { | |
| it("should set listState to Empty when emptyList$ emits true", () => { | ||
| sendItemsServiceEmptyList$.next(true); | ||
| fixture.detectChanges(); | ||
| expect(component["listState"]).toBe(SendState.Empty); | ||
| expect(component["listState"]).toBe(component["sendState"].Empty); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Why are we modifying this? The |
||
| }); | ||
|
|
||
| it("should set listState to NoResults when noFilteredResults$ emits true", () => { | ||
| sendItemsServiceNoFilteredResults$.next(true); | ||
| fixture.detectChanges(); | ||
| expect(component["listState"]).toBe(SendState.NoResults); | ||
| expect(component["listState"]).toBe(component["sendState"].NoResults); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Why are we modifying this? The |
||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,12 +38,16 @@ import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-heade | |
| import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; | ||
| import { VaultFadeInOutSkeletonComponent } from "../../../vault/popup/components/vault-fade-in-out-skeleton/vault-fade-in-out-skeleton.component"; | ||
|
|
||
| // FIXME: update to use a const object instead of a typescript enum | ||
| // eslint-disable-next-line @bitwarden/platform/no-enums | ||
| export enum SendState { | ||
| Empty, | ||
| NoResults, | ||
| } | ||
| /** A state of the Send list UI. */ | ||
| export const SendState = Object.freeze({ | ||
| /** No sends exist for the current filter (file or text). */ | ||
| Empty: "Empty", | ||
| /** Sends exist, but none match the current filter/search. */ | ||
| NoResults: "NoResults", | ||
| } as const); | ||
|
|
||
| /** A state of the Send list UI. */ | ||
| export type SendState = (typeof SendState)[keyof typeof SendState]; | ||
|
|
||
| // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush | ||
| // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection | ||
|
|
@@ -114,6 +118,11 @@ export class SendV2Component implements OnDestroy { | |
|
|
||
| protected sendsDisabled = false; | ||
|
|
||
| private readonly sendTypeTitles: Record<SendType, string> = { | ||
| [SendType.File]: "fileSends", | ||
| [SendType.Text]: "textSends", | ||
| }; | ||
|
|
||
| constructor( | ||
| protected sendItemsService: SendItemsService, | ||
| protected sendListFiltersService: SendListFiltersService, | ||
|
|
@@ -130,7 +139,7 @@ export class SendV2Component implements OnDestroy { | |
| .pipe(takeUntilDestroyed()) | ||
| .subscribe(([emptyList, noFilteredResults, currentFilter]) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to add types for the args |
||
| if (currentFilter?.sendType !== null) { | ||
| this.title = `${this.sendType[currentFilter.sendType].toLowerCase()}Sends`; | ||
| this.title = this.sendTypeTitles[currentFilter.sendType] ?? "allSends"; | ||
| } else { | ||
| this.title = "allSends"; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could be this to avoid nested if conditions if (currentFilter?.sendType != null) { this.title = titleMap[currentFilter.sendType] ?? 'allSends'; |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import { isDatePreset, asDatePreset, nameOfDatePreset } from "./add-edit.component"; | ||
|
|
||
| // Organized by unit: each describe block focuses on a single utility's behavior. | ||
| describe("isDatePreset", () => { | ||
| it("returns true for all valid DatePreset values (numbers, 'never', and Custom)", () => { | ||
| const validPresets: Array<any> = [ | ||
| 1, // OneHour | ||
| 24, // OneDay | ||
| 48, // TwoDays | ||
| 72, // ThreeDays | ||
| 168, // SevenDays | ||
| 720, // ThirtyDays | ||
| 0, // Custom | ||
| "never", | ||
| ]; | ||
| validPresets.forEach((preset) => { | ||
| expect(isDatePreset(preset)).toBe(true); | ||
| }); | ||
| }); | ||
|
|
||
| it("returns false for invalid values", () => { | ||
| const invalidPresets: Array<any> = [5, -1, 999, null, undefined, "foo", {}, []]; | ||
| invalidPresets.forEach((preset) => { | ||
| expect(isDatePreset(preset)).toBe(false); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe("asDatePreset", () => { | ||
| it("returns the same value for valid DatePreset inputs", () => { | ||
| const validPresets: Array<any> = [ | ||
| 1, // OneHour | ||
| 24, // OneDay | ||
| 48, // TwoDays | ||
| 72, // ThreeDays | ||
| 168, // SevenDays | ||
| 720, // ThirtyDays | ||
| 0, // Custom | ||
| "never", | ||
| ]; | ||
| validPresets.forEach((preset) => { | ||
| expect(asDatePreset(preset)).toBe(preset); | ||
| }); | ||
| }); | ||
|
|
||
| it("returns undefined for invalid inputs", () => { | ||
| const invalidPresets: Array<any> = [5, -1, 999, null, undefined, "foo", {}, []]; | ||
| invalidPresets.forEach((preset) => { | ||
| expect(asDatePreset(preset)).toBeUndefined(); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe("nameOfDatePreset", () => { | ||
| it("returns the correct key for valid DatePreset values", () => { | ||
| expect(nameOfDatePreset(1)).toBe("OneHour"); | ||
| expect(nameOfDatePreset(0)).toBe("Custom"); | ||
| expect(nameOfDatePreset("never")).toBe("Never"); | ||
| }); | ||
|
|
||
| it("returns undefined for invalid inputs", () => { | ||
| const invalidPresets: Array<any> = [5, -1, 999, null, undefined, "foo", {}, []]; | ||
| invalidPresets.forEach((preset) => { | ||
| expect(nameOfDatePreset(preset)).toBeUndefined(); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,25 +37,64 @@ import { SendService } from "@bitwarden/common/tools/send/services/send.service. | |
| import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; | ||
| import { DialogService, ToastService } from "@bitwarden/components"; | ||
|
|
||
| // Value = hours | ||
| // FIXME: update to use a const object instead of a typescript enum | ||
| // eslint-disable-next-line @bitwarden/platform/no-enums | ||
| enum DatePreset { | ||
| OneHour = 1, | ||
| OneDay = 24, | ||
| TwoDays = 48, | ||
| ThreeDays = 72, | ||
| SevenDays = 168, | ||
| ThirtyDays = 720, | ||
| Custom = 0, | ||
| Never = null, | ||
| } | ||
| const DatePreset = Object.freeze({ | ||
| /** One-hour duration. */ | ||
| OneHour: 1, | ||
| /** One-day duration (24 hours). */ | ||
| OneDay: 24, | ||
| /** Two-day duration (48 hours). */ | ||
| TwoDays: 48, | ||
| /** Three-day duration (72 hours). */ | ||
| ThreeDays: 72, | ||
| /** Seven-day duration (168 hours). */ | ||
| SevenDays: 168, | ||
| /** Thirty-day duration (720 hours). */ | ||
| ThirtyDays: 720, | ||
| /** Custom date/time, provided by the user. */ | ||
| Custom: 0, | ||
| /** No expiration. */ | ||
| Never: "never", | ||
| } as const); | ||
|
|
||
| /** A preset duration (in hours) for expiration/deletion. */ | ||
| export type DatePreset = (typeof DatePreset)[Exclude<keyof typeof DatePreset, "Never">] | "never"; | ||
|
|
||
| interface DatePresetSelectOption { | ||
| name: string; | ||
| value: DatePreset; | ||
| } | ||
|
|
||
| const namesByDatePreset = new Map<DatePreset, keyof typeof DatePreset>( | ||
| Object.entries(DatePreset).map(([k, v]) => [v, k as keyof typeof DatePreset]), | ||
| ); | ||
|
|
||
| /** | ||
| * Checks if a value is a valid DatePreset. | ||
| * @param value - The value to check. | ||
| * @returns True if the value is a valid DatePreset, false otherwise. | ||
| */ | ||
| export function isDatePreset(value: unknown): value is DatePreset { | ||
| return namesByDatePreset.has(value as DatePreset); | ||
| } | ||
|
|
||
| /** | ||
| * Converts a value to a DatePreset if it is valid. | ||
| * @param value - The value to convert. | ||
| * @returns The value as a DatePreset if valid, otherwise undefined. | ||
| */ | ||
| export function asDatePreset(value: unknown): DatePreset | undefined { | ||
| return isDatePreset(value) ? (value as DatePreset) : undefined; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the name of a DatePreset value. | ||
| * @param value - The DatePreset value to get the name for. | ||
| * @returns The name of the DatePreset value, or undefined if not found. | ||
| */ | ||
| export function nameOfDatePreset(value: DatePreset): keyof typeof DatePreset | undefined { | ||
| return namesByDatePreset.get(value); | ||
| } | ||
|
|
||
| @Directive() | ||
| export class AddEditComponent implements OnInit, OnDestroy { | ||
| // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals | ||
|
|
@@ -86,7 +125,7 @@ export class AddEditComponent implements OnInit, OnDestroy { | |
| ]; | ||
|
|
||
| expirationDatePresets: DatePresetSelectOption[] = [ | ||
| { name: this.i18nService.t("never"), value: DatePreset.Never }, | ||
| { name: this.i18nService.t("never"), value: "never" }, | ||
| ...this.deletionDatePresets, | ||
| ]; | ||
|
|
||
|
|
@@ -126,7 +165,10 @@ export class AddEditComponent implements OnInit, OnDestroy { | |
| type: [], | ||
| defaultExpirationDateTime: [], | ||
| defaultDeletionDateTime: ["", Validators.required], | ||
| selectedDeletionDatePreset: [DatePreset.SevenDays, Validators.required], | ||
| selectedDeletionDatePreset: this.formBuilder.nonNullable.control<DatePreset>( | ||
| DatePreset.SevenDays, | ||
| { validators: [Validators.required] }, | ||
| ), | ||
| selectedExpirationDatePreset: [], | ||
| }); | ||
|
|
||
|
|
@@ -539,40 +581,42 @@ export class AddEditComponent implements OnInit, OnDestroy { | |
| } | ||
|
|
||
| get formattedExpirationDate(): string { | ||
| switch (this.formGroup.controls.selectedExpirationDatePreset.value as DatePreset) { | ||
| case DatePreset.Never: | ||
| const rawPreset = this.formGroup.controls.selectedExpirationDatePreset.value; | ||
| const preset = asDatePreset(rawPreset); | ||
| if (!isDatePreset(preset)) { | ||
| return null; | ||
| } | ||
|
Comment on lines
+585
to
+588
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. โ๏ธ You don't need both |
||
| if (preset === DatePreset.Custom) { | ||
| if (!this.formGroup.controls.defaultExpirationDateTime.value) { | ||
| return null; | ||
| case DatePreset.Custom: | ||
| if (!this.formGroup.controls.defaultExpirationDateTime.value) { | ||
| return null; | ||
| } | ||
| return this.formGroup.controls.defaultExpirationDateTime.value; | ||
| default: { | ||
| const now = new Date(); | ||
| const milliseconds = now.setTime( | ||
| now.getTime() + | ||
| (this.formGroup.controls.selectedExpirationDatePreset.value as number) * 60 * 60 * 1000, | ||
| ); | ||
| return new Date(milliseconds).toString(); | ||
| } | ||
| return this.formGroup.controls.defaultExpirationDateTime.value; | ||
| } | ||
| if (typeof preset === "number") { | ||
| const now = new Date(); | ||
| const milliseconds = now.setTime(now.getTime() + preset * 60 * 60 * 1000); | ||
| return new Date(milliseconds).toString(); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| get formattedDeletionDate(): string { | ||
| switch (this.formGroup.controls.selectedDeletionDatePreset.value as DatePreset) { | ||
| case DatePreset.Never: | ||
| this.formGroup.controls.selectedDeletionDatePreset.patchValue(DatePreset.SevenDays); | ||
| return this.formattedDeletionDate; | ||
| case DatePreset.Custom: | ||
| return this.formGroup.controls.defaultDeletionDateTime.value; | ||
| default: { | ||
| const now = new Date(); | ||
| const milliseconds = now.setTime( | ||
| now.getTime() + | ||
| (this.formGroup.controls.selectedDeletionDatePreset.value as number) * 60 * 60 * 1000, | ||
| ); | ||
| return new Date(milliseconds).toString(); | ||
| } | ||
| const rawPreset = this.formGroup.controls.selectedDeletionDatePreset.value; | ||
| const preset = asDatePreset(rawPreset); // what if this fails? | ||
| if (!isDatePreset(preset)) { | ||
| // is this reasonable as a safeguard? values come from a select control which should be trusted | ||
| return null; | ||
| } | ||
| if (preset === "never") { | ||
| //FIXME Getters should not have side effects | ||
| this.formGroup.controls.selectedDeletionDatePreset.patchValue(DatePreset.SevenDays); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| return this.formattedDeletionDate; | ||
| } | ||
| if (preset === DatePreset.Custom) { | ||
| return this.formGroup.controls.defaultDeletionDateTime.value; | ||
| } | ||
| const now = new Date(); | ||
| const milliseconds = now.setTime(now.getTime() + preset * 60 * 60 * 1000); | ||
| return new Date(milliseconds).toString(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,10 @@ | ||
| // FIXME: update to use a const object instead of a typescript enum | ||
| // eslint-disable-next-line @bitwarden/platform/no-enums | ||
| export enum SendType { | ||
| Text = 0, | ||
| File = 1, | ||
| } | ||
| /** A type of Send. */ | ||
| export const SendType = Object.freeze({ | ||
| /** Send contains plain text. */ | ||
| Text: 0, | ||
| /** Send contains a file. */ | ||
| File: 1, | ||
| } as const); | ||
|
|
||
| /** A type of Send. */ | ||
| export type SendType = (typeof SendType)[keyof typeof SendType]; |
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if these tests actually test anything valuable beyond that freeze and constant behave as you'd expect. But also no harm in keeping the tests. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { SendItemDialogResult } from "./send-add-edit-dialog.component"; | ||
|
|
||
| describe("SendItemDialogResult", () => { | ||
| it("has the expected result values", () => { | ||
| expect(SendItemDialogResult.Saved).toBe("saved"); | ||
| expect(SendItemDialogResult.Deleted).toBe("deleted"); | ||
| }); | ||
|
|
||
| it("has exactly two result values", () => { | ||
| const keys = Object.keys(SendItemDialogResult); | ||
| expect(keys.length).toBe(2); | ||
| expect(keys).toContain("Saved"); | ||
| expect(keys).toContain("Deleted"); | ||
| }); | ||
|
|
||
| it("is frozen and immutable", () => { | ||
| expect(Object.isFrozen(SendItemDialogResult)).toBe(true); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[SendType.Text, SendType.File].indexOf(sendTypeValue) !== -1
could be