mirror of
https://github.com/bitwarden/browser
synced 2026-02-08 20:50:28 +00:00
Merge branch 'desktop/PM-27793/create-new-vault-component' of https://github.com/lxiong-livefront/clients into desktop/PM-27793/create-new-vault-component
This commit is contained in:
10
.github/renovate.json5
vendored
10
.github/renovate.json5
vendored
@@ -119,7 +119,7 @@
|
||||
"rimraf",
|
||||
"ssh-encoding",
|
||||
"ssh-key",
|
||||
"@storybook/web-components-webpack5",
|
||||
"@storybook/web-components-vite",
|
||||
"tabbable",
|
||||
"tldts",
|
||||
"wait-on",
|
||||
@@ -311,26 +311,24 @@
|
||||
"@compodoc/compodoc",
|
||||
"@ng-select/ng-select",
|
||||
"@storybook/addon-a11y",
|
||||
"@storybook/addon-actions",
|
||||
"@storybook/addon-designs",
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-interactions",
|
||||
"@storybook/addon-docs",
|
||||
"@storybook/addon-links",
|
||||
"@storybook/test-runner",
|
||||
"@storybook/addon-themes",
|
||||
"@storybook/angular",
|
||||
"@storybook/manager-api",
|
||||
"@storybook/theming",
|
||||
"@types/react",
|
||||
"autoprefixer",
|
||||
"bootstrap",
|
||||
"chromatic",
|
||||
"ngx-toastr",
|
||||
"path-browserify",
|
||||
"react",
|
||||
"react-dom",
|
||||
"remark-gfm",
|
||||
"storybook",
|
||||
"tailwindcss",
|
||||
"vite-tsconfig-paths",
|
||||
"zone.js",
|
||||
"@tailwindcss/container-queries",
|
||||
],
|
||||
|
||||
@@ -28,15 +28,13 @@ const config: StorybookConfig = {
|
||||
],
|
||||
addons: [
|
||||
getAbsolutePath("@storybook/addon-links"),
|
||||
getAbsolutePath("@storybook/addon-essentials"),
|
||||
getAbsolutePath("@storybook/addon-a11y"),
|
||||
getAbsolutePath("@storybook/addon-designs"),
|
||||
getAbsolutePath("@storybook/addon-interactions"),
|
||||
getAbsolutePath("@storybook/addon-themes"),
|
||||
{
|
||||
// @storybook/addon-docs is part of @storybook/addon-essentials
|
||||
// eslint-disable-next-line storybook/no-uninstalled-addons
|
||||
name: "@storybook/addon-docs",
|
||||
|
||||
name: getAbsolutePath("@storybook/addon-docs"),
|
||||
options: {
|
||||
mdxPluginOptions: {
|
||||
mdxCompileOptions: {
|
||||
@@ -60,6 +58,10 @@ const config: StorybookConfig = {
|
||||
webpackFinal: async (config, { configType }) => {
|
||||
if (config.resolve) {
|
||||
config.resolve.plugins = [new TsconfigPathsPlugin()] as any;
|
||||
config.resolve.fallback = {
|
||||
...config.resolve.fallback,
|
||||
path: require.resolve("path-browserify"),
|
||||
};
|
||||
}
|
||||
return config;
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { addons } from "@storybook/manager-api";
|
||||
import { create } from "@storybook/theming/create";
|
||||
import { addons } from "storybook/manager-api";
|
||||
import { create } from "storybook/theming";
|
||||
|
||||
const lightTheme = create({
|
||||
base: "light",
|
||||
|
||||
@@ -49,7 +49,7 @@ const preview: Preview = {
|
||||
},
|
||||
},
|
||||
backgrounds: {
|
||||
disable: true,
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
|
||||
26
angular.json
26
angular.json
@@ -220,5 +220,31 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"type": "component"
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"type": "directive"
|
||||
},
|
||||
"@schematics/angular:service": {
|
||||
"type": "service"
|
||||
},
|
||||
"@schematics/angular:guard": {
|
||||
"typeSeparator": "."
|
||||
},
|
||||
"@schematics/angular:interceptor": {
|
||||
"typeSeparator": "."
|
||||
},
|
||||
"@schematics/angular:module": {
|
||||
"typeSeparator": "."
|
||||
},
|
||||
"@schematics/angular:pipe": {
|
||||
"typeSeparator": "."
|
||||
},
|
||||
"@schematics/angular:resolver": {
|
||||
"typeSeparator": "."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { createRequire } from "module";
|
||||
import { dirname, join, resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
import type { StorybookConfig } from "@storybook/web-components-webpack5";
|
||||
import type { StorybookConfig } from "@storybook/web-components-vite";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
|
||||
|
||||
const currentFile = fileURLToPath(import.meta.url);
|
||||
const currentDirectory = dirname(currentFile);
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
@@ -18,10 +14,8 @@ const config: StorybookConfig = {
|
||||
stories: ["../lit-stories/**/*.lit-stories.@(js|jsx|ts|tsx)", "../lit-stories/**/*.mdx"],
|
||||
addons: [
|
||||
getAbsolutePath("@storybook/addon-links"),
|
||||
getAbsolutePath("@storybook/addon-essentials"),
|
||||
getAbsolutePath("@storybook/addon-a11y"),
|
||||
getAbsolutePath("@storybook/addon-designs"),
|
||||
getAbsolutePath("@storybook/addon-interactions"),
|
||||
{
|
||||
name: "@storybook/addon-docs",
|
||||
options: {
|
||||
@@ -34,10 +28,8 @@ const config: StorybookConfig = {
|
||||
},
|
||||
],
|
||||
framework: {
|
||||
name: getAbsolutePath("@storybook/web-components-webpack5"),
|
||||
options: {
|
||||
legacyRootApi: true,
|
||||
},
|
||||
name: getAbsolutePath("@storybook/web-components-vite"),
|
||||
options: {},
|
||||
},
|
||||
core: {
|
||||
disableTelemetry: true,
|
||||
@@ -46,33 +38,12 @@ const config: StorybookConfig = {
|
||||
...existingConfig,
|
||||
FLAGS: JSON.stringify({}),
|
||||
}),
|
||||
webpackFinal: async (config) => {
|
||||
if (config.resolve) {
|
||||
config.resolve.plugins = [
|
||||
new TsconfigPathsPlugin({
|
||||
configFile: resolve(currentDirectory, "../../../../../tsconfig.json"),
|
||||
}),
|
||||
] as any;
|
||||
}
|
||||
|
||||
if (config.module && config.module.rules) {
|
||||
config.module.rules.push({
|
||||
test: /\.(ts|tsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("ts-loader"),
|
||||
},
|
||||
],
|
||||
});
|
||||
config.module.rules.push({
|
||||
test: /\.scss$/,
|
||||
use: [require.resolve("css-loader"), require.resolve("sass-loader")],
|
||||
});
|
||||
}
|
||||
return config;
|
||||
viteFinal: async (config) => {
|
||||
return {
|
||||
...config,
|
||||
plugins: [...(config.plugins ?? []), tsconfigPaths()],
|
||||
};
|
||||
},
|
||||
docs: {},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs";
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./action-button.lit-stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs";
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./badge-button.lit-stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs";
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./body.lit-stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs";
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./close-button.lit-stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs";
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./edit-button.lit-stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs";
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./footer.lit-stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs";
|
||||
import { Meta, Controls, Primary } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./header.lit-stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./icons.lit-stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Story, Canvas } from "@storybook/addon-docs";
|
||||
import { Meta, Story, Canvas } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./popup-layout.stories";
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { DOCUMENT } from "@angular/common";
|
||||
import { inject, Inject, Injectable } from "@angular/core";
|
||||
import { inject, Inject, Injectable, DOCUMENT } from "@angular/core";
|
||||
|
||||
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
|
||||
@@ -0,0 +1,425 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import {
|
||||
CipherViewLike,
|
||||
CipherViewLikeUtils,
|
||||
} from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { IconButtonModule, ItemModule, MenuModule } from "@bitwarden/components";
|
||||
import { CipherListView, CopyableCipherFields } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service";
|
||||
|
||||
import { ItemCopyActionsComponent } from "./item-copy-actions.component";
|
||||
|
||||
describe("ItemCopyActionsComponent", () => {
|
||||
let fixture: ComponentFixture<ItemCopyActionsComponent>;
|
||||
let component: ItemCopyActionsComponent;
|
||||
|
||||
let i18nService: jest.Mocked<I18nService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
i18nService = {
|
||||
t: jest.fn((key: string) => `translated-${key}`),
|
||||
} as unknown as jest.Mocked<I18nService>;
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
ItemModule,
|
||||
IconButtonModule,
|
||||
MenuModule,
|
||||
ItemCopyActionsComponent, // standalone
|
||||
],
|
||||
providers: [
|
||||
{ provide: I18nService, useValue: i18nService },
|
||||
{
|
||||
provide: VaultPopupCopyButtonsService,
|
||||
useValue: {
|
||||
showQuickCopyActions$: of(true),
|
||||
} satisfies Partial<VaultPopupCopyButtonsService>,
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ItemCopyActionsComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
// Default cipher so tests can override as needed
|
||||
component.cipher = {
|
||||
name: "My cipher",
|
||||
viewPassword: true,
|
||||
login: { username: null, password: null, totp: null },
|
||||
card: { code: null, number: null },
|
||||
identity: {
|
||||
fullAddressForCopy: null,
|
||||
email: null,
|
||||
username: null,
|
||||
phone: null,
|
||||
},
|
||||
sshKey: {
|
||||
privateKey: null,
|
||||
publicKey: null,
|
||||
keyFingerprint: null,
|
||||
},
|
||||
notes: null,
|
||||
copyableFields: [],
|
||||
} as unknown as CipherViewLike;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("findSingleCopyableItem", () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(CipherViewLikeUtils, "hasCopyableValue")
|
||||
.mockImplementation(
|
||||
(cipher: CipherViewLike & { __copyable?: Record<string, boolean> }, field) => {
|
||||
return Boolean(cipher.__copyable?.[field]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("returns the single item with value and translates its key", () => {
|
||||
const items = [
|
||||
{ key: "copyUsername", field: "username" as const },
|
||||
{ key: "copyPassword", field: "password" as const },
|
||||
];
|
||||
|
||||
(component.cipher as any).__copyable = {
|
||||
username: true,
|
||||
password: false,
|
||||
};
|
||||
|
||||
const result = component.findSingleCopyableItem(items);
|
||||
|
||||
expect(result).toEqual({
|
||||
key: "translated-copyUsername",
|
||||
field: "username",
|
||||
});
|
||||
expect(i18nService.t).toHaveBeenCalledWith("copyUsername");
|
||||
});
|
||||
|
||||
it("returns null when no items have a value", () => {
|
||||
const items = [
|
||||
{ key: "copyUsername", field: "username" as const },
|
||||
{ key: "copyPassword", field: "password" as const },
|
||||
];
|
||||
|
||||
(component.cipher as any).__copyable = {
|
||||
username: false,
|
||||
password: false,
|
||||
};
|
||||
|
||||
const result = component.findSingleCopyableItem(items);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null when more than one item has a value", () => {
|
||||
const items = [
|
||||
{ key: "copyUsername", field: "username" as const },
|
||||
{ key: "copyPassword", field: "password" as const },
|
||||
];
|
||||
|
||||
(component.cipher as any).__copyable = {
|
||||
username: true,
|
||||
password: true,
|
||||
};
|
||||
|
||||
const result = component.findSingleCopyableItem(items);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("singleCopyableLogin", () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(CipherViewLikeUtils, "hasCopyableValue")
|
||||
.mockImplementation(
|
||||
(cipher: CipherViewLike & { __copyable?: Record<string, boolean> }, field) => {
|
||||
return Boolean(cipher.__copyable?.[field]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("returns username with special-case logic when password is hidden and both username/password exist and no totp", () => {
|
||||
(component.cipher as CipherView).viewPassword = false;
|
||||
|
||||
(component.cipher as any).__copyable = {
|
||||
username: true,
|
||||
password: true,
|
||||
totp: false,
|
||||
};
|
||||
|
||||
const result = component.singleCopyableLogin;
|
||||
|
||||
expect(result).toEqual({
|
||||
key: "translated-copyUsername",
|
||||
field: "username",
|
||||
});
|
||||
expect(i18nService.t).toHaveBeenCalledWith("copyUsername");
|
||||
});
|
||||
|
||||
it("returns null when password is hidden but multiple fields exist, ensuring username and totp are shown in the menu UI ", () => {
|
||||
(component.cipher as CipherView).viewPassword = false;
|
||||
|
||||
(component.cipher as any).__copyable = {
|
||||
username: true,
|
||||
password: true,
|
||||
totp: true,
|
||||
};
|
||||
|
||||
const result = component.singleCopyableLogin;
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("falls back to findSingleCopyableItem when password is visible", () => {
|
||||
const findSingleCopyableItemSpy = jest.spyOn(component, "findSingleCopyableItem");
|
||||
(component.cipher as CipherView).viewPassword = true;
|
||||
|
||||
void component.singleCopyableLogin;
|
||||
expect(findSingleCopyableItemSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("singleCopyableCard", () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(CipherViewLikeUtils, "hasCopyableValue")
|
||||
.mockImplementation(
|
||||
(cipher: CipherViewLike & { __copyable?: Record<string, boolean> }, field) => {
|
||||
return Boolean(cipher.__copyable?.[field]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("returns security code when it is the only available card value", () => {
|
||||
(component.cipher as any).__copyable = {
|
||||
securityCode: true,
|
||||
cardNumber: false,
|
||||
};
|
||||
|
||||
const result = component.singleCopyableCard;
|
||||
|
||||
expect(result).toEqual({
|
||||
key: "translated-securityCode",
|
||||
field: "securityCode",
|
||||
});
|
||||
expect(i18nService.t).toHaveBeenCalledWith("securityCode");
|
||||
});
|
||||
|
||||
it("returns null when both card number and security code are available", () => {
|
||||
(component.cipher as any).__copyable = {
|
||||
securityCode: true,
|
||||
cardNumber: true,
|
||||
};
|
||||
|
||||
const result = component.singleCopyableCard;
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("singleCopyableIdentity", () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(CipherViewLikeUtils, "hasCopyableValue")
|
||||
.mockImplementation(
|
||||
(cipher: CipherViewLike & { __copyable?: Record<string, boolean> }, field) => {
|
||||
return Boolean(cipher.__copyable?.[field]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("returns the only copyable identity field", () => {
|
||||
(component.cipher as any).__copyable = {
|
||||
address: false,
|
||||
email: true,
|
||||
username: false,
|
||||
phone: false,
|
||||
};
|
||||
|
||||
const result = component.singleCopyableIdentity;
|
||||
|
||||
expect(result).toEqual({
|
||||
key: "translated-email",
|
||||
field: "email",
|
||||
});
|
||||
expect(i18nService.t).toHaveBeenCalledWith("email");
|
||||
});
|
||||
|
||||
it("returns null when multiple identity fields are available", () => {
|
||||
(component.cipher as any).__copyable = {
|
||||
address: true,
|
||||
email: true,
|
||||
username: false,
|
||||
phone: false,
|
||||
};
|
||||
|
||||
const result = component.singleCopyableIdentity;
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("has*Values in non-list view", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(CipherViewLikeUtils, "isCipherListView").mockReturnValue(false);
|
||||
});
|
||||
|
||||
it("computes hasLoginValues from login fields", () => {
|
||||
(component.cipher as CipherView).login = {
|
||||
username: "user",
|
||||
password: null,
|
||||
totp: null,
|
||||
} as any;
|
||||
|
||||
expect(component.hasLoginValues).toBe(true);
|
||||
|
||||
(component.cipher as CipherView).login = {
|
||||
username: null,
|
||||
password: null,
|
||||
totp: null,
|
||||
} as any;
|
||||
|
||||
expect(component.hasLoginValues).toBe(false);
|
||||
});
|
||||
|
||||
it("computes hasCardValues from card fields", () => {
|
||||
(component.cipher as CipherView).card = { code: "123", number: null } as any;
|
||||
|
||||
expect(component.hasCardValues).toBe(true);
|
||||
|
||||
(component.cipher as CipherView).card = { code: null, number: null } as any;
|
||||
|
||||
expect(component.hasCardValues).toBe(false);
|
||||
});
|
||||
|
||||
it("computes hasIdentityValues from identity fields", () => {
|
||||
(component.cipher as CipherView).identity = {
|
||||
fullAddressForCopy: null,
|
||||
email: "test@example.com",
|
||||
username: null,
|
||||
phone: null,
|
||||
} as any;
|
||||
|
||||
expect(component.hasIdentityValues).toBe(true);
|
||||
|
||||
(component.cipher as CipherView).identity = {
|
||||
fullAddressForCopy: null,
|
||||
email: null,
|
||||
username: null,
|
||||
phone: null,
|
||||
} as any;
|
||||
|
||||
expect(component.hasIdentityValues).toBe(false);
|
||||
});
|
||||
|
||||
it("computes hasSecureNoteValue from notes", () => {
|
||||
(component.cipher as CipherView).notes = "Some note" as any;
|
||||
expect(component.hasSecureNoteValue).toBe(true);
|
||||
|
||||
(component.cipher as CipherView).notes = null as any;
|
||||
expect(component.hasSecureNoteValue).toBe(false);
|
||||
});
|
||||
|
||||
it("computes hasSshKeyValues from sshKey fields", () => {
|
||||
(component.cipher as CipherView).sshKey = {
|
||||
privateKey: "priv",
|
||||
publicKey: null,
|
||||
keyFingerprint: null,
|
||||
} as any;
|
||||
|
||||
expect(component.hasSshKeyValues).toBe(true);
|
||||
|
||||
(component.cipher as CipherView).sshKey = {
|
||||
privateKey: null,
|
||||
publicKey: null,
|
||||
keyFingerprint: null,
|
||||
} as any;
|
||||
|
||||
expect(component.hasSshKeyValues).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("has*Values in list view", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(CipherViewLikeUtils, "isCipherListView").mockReturnValue(true);
|
||||
});
|
||||
|
||||
it("uses copyableFields for login values", () => {
|
||||
(component.cipher as CipherListView).copyableFields = [
|
||||
"LoginUsername",
|
||||
"CardNumber",
|
||||
] as CopyableCipherFields[];
|
||||
|
||||
expect(component.hasLoginValues).toBe(true);
|
||||
|
||||
(component.cipher as CipherListView).copyableFields = [
|
||||
"CardNumber",
|
||||
] as CopyableCipherFields[];
|
||||
|
||||
expect(component.hasLoginValues).toBe(false);
|
||||
});
|
||||
|
||||
it("uses copyableFields for card values", () => {
|
||||
(component.cipher as CipherListView).copyableFields = [
|
||||
"CardSecurityCode",
|
||||
] as CopyableCipherFields[];
|
||||
|
||||
expect(component.hasCardValues).toBe(true);
|
||||
|
||||
(component.cipher as CipherListView).copyableFields = [
|
||||
"LoginUsername",
|
||||
] as CopyableCipherFields[];
|
||||
|
||||
expect(component.hasCardValues).toBe(false);
|
||||
});
|
||||
|
||||
it("uses copyableFields for identity values", () => {
|
||||
(component.cipher as CipherListView).copyableFields = [
|
||||
"IdentityEmail",
|
||||
] as CopyableCipherFields[];
|
||||
|
||||
expect(component.hasIdentityValues).toBe(true);
|
||||
|
||||
(component.cipher as CipherListView).copyableFields = [
|
||||
"LoginUsername",
|
||||
] as CopyableCipherFields[];
|
||||
|
||||
expect(component.hasIdentityValues).toBe(false);
|
||||
});
|
||||
|
||||
it("uses copyableFields for secure note value", () => {
|
||||
(component.cipher as CipherListView).copyableFields = [
|
||||
"SecureNotes",
|
||||
] as CopyableCipherFields[];
|
||||
expect(component.hasSecureNoteValue).toBe(true);
|
||||
|
||||
(component.cipher as CipherListView).copyableFields = [
|
||||
"LoginUsername",
|
||||
] as CopyableCipherFields[];
|
||||
expect(component.hasSecureNoteValue).toBe(false);
|
||||
});
|
||||
|
||||
it("uses copyableFields for ssh key values", () => {
|
||||
(component.cipher as CipherListView).copyableFields = ["SshKey"] as CopyableCipherFields[];
|
||||
expect(component.hasSshKeyValues).toBe(true);
|
||||
|
||||
(component.cipher as CipherListView).copyableFields = [
|
||||
"LoginUsername",
|
||||
] as CopyableCipherFields[];
|
||||
expect(component.hasSshKeyValues).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -54,17 +54,20 @@ export class ItemCopyActionsComponent {
|
||||
{ key: "copyPassword", field: "password" },
|
||||
{ key: "copyVerificationCode", field: "totp" },
|
||||
];
|
||||
// If both the password and username are visible but the password is hidden, return the username
|
||||
// If both the password and username are visible but the password is hidden and there's no
|
||||
// totp code to copy return the username
|
||||
if (
|
||||
!this.cipher.viewPassword &&
|
||||
CipherViewLikeUtils.hasCopyableValue(this.cipher, "username") &&
|
||||
CipherViewLikeUtils.hasCopyableValue(this.cipher, "password")
|
||||
CipherViewLikeUtils.hasCopyableValue(this.cipher, "password") &&
|
||||
!CipherViewLikeUtils.hasCopyableValue(this.cipher, "totp")
|
||||
) {
|
||||
return {
|
||||
key: this.i18nService.t("copyUsername"),
|
||||
field: "username" as const,
|
||||
};
|
||||
}
|
||||
|
||||
return this.findSingleCopyableItem(loginItems);
|
||||
}
|
||||
|
||||
|
||||
8
apps/desktop/desktop_native/Cargo.lock
generated
8
apps/desktop/desktop_native/Cargo.lock
generated
@@ -1863,9 +1863,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mockall"
|
||||
version = "0.13.1"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2"
|
||||
checksum = "f58d964098a5f9c6b63d0798e5372fd04708193510a7af313c22e9f29b7b620b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"downcast",
|
||||
@@ -1877,9 +1877,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mockall_derive"
|
||||
version = "0.13.1"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898"
|
||||
checksum = "ca41ce716dda6a9be188b385aa78ee5260fc25cd3802cb2a8afdc6afbe6b6dbf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
|
||||
@@ -9,7 +9,7 @@ publish.workspace = true
|
||||
anyhow = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
mockall = "=0.13.1"
|
||||
mockall = "=0.14.0"
|
||||
serial_test = "=3.2.0"
|
||||
tracing.workspace = true
|
||||
windows = { workspace = true, features = [
|
||||
|
||||
@@ -272,6 +272,7 @@ mod tests {
|
||||
#[serial]
|
||||
fn send_input_succeeds() {
|
||||
let ctxi = MockInputOperations::send_input_context();
|
||||
ctxi.checkpoint();
|
||||
ctxi.expect().returning(|_| 1);
|
||||
|
||||
send_input::<MockInputOperations, MockErrorOperations>(vec![build_unicode_input(
|
||||
@@ -279,6 +280,8 @@ mod tests {
|
||||
0,
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
drop(ctxi);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -288,9 +291,11 @@ mod tests {
|
||||
)]
|
||||
fn send_input_fails_sent_zero() {
|
||||
let ctxi = MockInputOperations::send_input_context();
|
||||
ctxi.checkpoint();
|
||||
ctxi.expect().returning(|_| 0);
|
||||
|
||||
let ctxge = MockErrorOperations::get_last_error_context();
|
||||
ctxge.checkpoint();
|
||||
ctxge.expect().returning(|| WIN32_ERROR(1));
|
||||
|
||||
send_input::<MockInputOperations, MockErrorOperations>(vec![build_unicode_input(
|
||||
@@ -298,6 +303,9 @@ mod tests {
|
||||
0,
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
drop(ctxge);
|
||||
drop(ctxi);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -305,9 +313,11 @@ mod tests {
|
||||
#[should_panic(expected = "SendInput does not match expected. sent: 2, expected: 1")]
|
||||
fn send_input_fails_sent_mismatch() {
|
||||
let ctxi = MockInputOperations::send_input_context();
|
||||
ctxi.checkpoint();
|
||||
ctxi.expect().returning(|_| 2);
|
||||
|
||||
let ctxge = MockErrorOperations::get_last_error_context();
|
||||
ctxge.checkpoint();
|
||||
ctxge.expect().returning(|| WIN32_ERROR(1));
|
||||
|
||||
send_input::<MockInputOperations, MockErrorOperations>(vec![build_unicode_input(
|
||||
@@ -315,5 +325,8 @@ mod tests {
|
||||
0,
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
drop(ctxge);
|
||||
drop(ctxi);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +186,7 @@ mod tests {
|
||||
let mut mock_handle = MockWindowHandleOperations::new();
|
||||
|
||||
let ctxse = MockErrorOperations::set_last_error_context();
|
||||
ctxse.checkpoint();
|
||||
ctxse
|
||||
.expect()
|
||||
.once()
|
||||
@@ -198,6 +199,7 @@ mod tests {
|
||||
.returning(|| Ok(0));
|
||||
|
||||
let ctxge = MockErrorOperations::get_last_error_context();
|
||||
ctxge.checkpoint();
|
||||
ctxge.expect().returning(|| WIN32_ERROR(0));
|
||||
|
||||
let len = get_window_title_length::<MockWindowHandleOperations, MockErrorOperations>(
|
||||
@@ -206,6 +208,9 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(len, 0);
|
||||
|
||||
drop(ctxge);
|
||||
drop(ctxse);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -215,6 +220,7 @@ mod tests {
|
||||
let mut mock_handle = MockWindowHandleOperations::new();
|
||||
|
||||
let ctxse = MockErrorOperations::set_last_error_context();
|
||||
ctxse.checkpoint();
|
||||
ctxse.expect().with(predicate::eq(0)).returning(|_| {});
|
||||
|
||||
mock_handle
|
||||
@@ -223,13 +229,18 @@ mod tests {
|
||||
.returning(|| Ok(0));
|
||||
|
||||
let ctxge = MockErrorOperations::get_last_error_context();
|
||||
ctxge.checkpoint();
|
||||
ctxge.expect().returning(|| WIN32_ERROR(1));
|
||||
|
||||
get_window_title_length::<MockWindowHandleOperations, MockErrorOperations>(&mock_handle)
|
||||
.unwrap();
|
||||
|
||||
drop(ctxge);
|
||||
drop(ctxse);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn get_window_title_succeeds() {
|
||||
let mut mock_handle = MockWindowHandleOperations::new();
|
||||
|
||||
@@ -246,11 +257,11 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(title.len(), 43); // That extra slot in the buffer for null char
|
||||
|
||||
assert_eq!(title, "*******************************************");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn get_window_title_returns_empty_string() {
|
||||
let mock_handle = MockWindowHandleOperations::new();
|
||||
|
||||
@@ -273,10 +284,13 @@ mod tests {
|
||||
.returning(|_| Ok(0));
|
||||
|
||||
let ctxge = MockErrorOperations::get_last_error_context();
|
||||
ctxge.checkpoint();
|
||||
ctxge.expect().returning(|| WIN32_ERROR(1));
|
||||
|
||||
get_window_title::<MockWindowHandleOperations, MockErrorOperations>(&mock_handle, 42)
|
||||
.unwrap();
|
||||
|
||||
drop(ctxge);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -290,9 +304,12 @@ mod tests {
|
||||
.returning(|_| Ok(0));
|
||||
|
||||
let ctxge = MockErrorOperations::get_last_error_context();
|
||||
ctxge.checkpoint();
|
||||
ctxge.expect().returning(|| WIN32_ERROR(0));
|
||||
|
||||
get_window_title::<MockWindowHandleOperations, MockErrorOperations>(&mock_handle, 42)
|
||||
.unwrap();
|
||||
|
||||
drop(ctxge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ describe("DesktopSideNavComponent", () => {
|
||||
expect(component.variant()).toBe("secondary");
|
||||
});
|
||||
|
||||
it("passes variant to bit-side-nav", () => {
|
||||
it.skip("passes variant to bit-side-nav", () => {
|
||||
fixture.componentRef.setInput("variant", "secondary");
|
||||
fixture.detectChanges();
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { DOCUMENT } from "@angular/common";
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
import { Inject, Injectable, DOCUMENT } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@bitwarden/web-vault",
|
||||
"version": "2025.12.0",
|
||||
"version": "2025.12.1",
|
||||
"scripts": {
|
||||
"build:oss": "webpack",
|
||||
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<org-switcher [filter]="orgFilter" [hideNewButton]="hideNewOrgButton$ | async"></org-switcher>
|
||||
<bit-nav-item
|
||||
icon="bwi-dashboard"
|
||||
*ngIf="organization.useAccessIntelligence && organization.canAccessReports"
|
||||
*ngIf="organization.canAccessReports"
|
||||
[text]="'accessIntelligence' | i18n"
|
||||
route="access-intelligence"
|
||||
></bit-nav-item>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import { action } from "storybook/actions";
|
||||
|
||||
import { AccessItemType, AccessItemView } from "./access-selector.models";
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { DOCUMENT } from "@angular/common";
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
import { Inject, Injectable, DOCUMENT } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { CommonModule, DOCUMENT } from "@angular/common";
|
||||
import { Component, Inject, OnDestroy, OnInit, ChangeDetectionStrategy } from "@angular/core";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import {
|
||||
Component,
|
||||
Inject,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
DOCUMENT,
|
||||
ChangeDetectionStrategy,
|
||||
} from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { map, Observable, of, tap } from "rxjs";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CommonModule, DOCUMENT } from "@angular/common";
|
||||
import { Component, ViewChildren, QueryList, ElementRef, inject } from "@angular/core";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, ViewChildren, QueryList, ElementRef, inject, DOCUMENT } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { debounceTime, fromEvent } from "rxjs";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DOCUMENT, NgIf } from "@angular/common";
|
||||
import { Component, DestroyRef, inject, OnDestroy, OnInit } from "@angular/core";
|
||||
import { NgIf } from "@angular/common";
|
||||
import { Component, DestroyRef, inject, OnDestroy, OnInit, DOCUMENT } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { Router, RouterModule } from "@angular/router";
|
||||
import { firstValueFrom, pairwise, startWith } from "rxjs";
|
||||
|
||||
@@ -71,6 +71,7 @@ export default tseslint.config(
|
||||
"@angular-eslint/no-output-on-prefix": 0,
|
||||
"@angular-eslint/no-output-rename": "error",
|
||||
"@angular-eslint/no-outputs-metadata-property": "error",
|
||||
"@angular-eslint/prefer-inject": 0,
|
||||
"@angular-eslint/prefer-on-push-component-change-detection": "error",
|
||||
"@angular-eslint/prefer-output-emitter-ref": "error",
|
||||
"@angular-eslint/prefer-signals": "error",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InjectFlags, InjectOptions, Injector, ProviderToken } from "@angular/core";
|
||||
import { InjectOptions, Injector, ProviderToken } from "@angular/core";
|
||||
|
||||
export class ModalInjector implements Injector {
|
||||
constructor(
|
||||
@@ -12,8 +12,8 @@ export class ModalInjector implements Injector {
|
||||
options: InjectOptions & { optional?: false },
|
||||
): T;
|
||||
get<T>(token: ProviderToken<T>, notFoundValue: null, options: InjectOptions): T;
|
||||
get<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions | InjectFlags): T;
|
||||
get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
|
||||
get<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions | null): T;
|
||||
get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: null): T;
|
||||
get(token: any, notFoundValue?: any): any;
|
||||
get(token: any, notFoundValue?: any, flags?: any): any {
|
||||
return this._additionalTokens.get(token) ?? this._parentInjector.get<any>(token, notFoundValue);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Story } from "@storybook/addon-docs";
|
||||
import { Meta, Story } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./input-password.stories.ts";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { importProvidersFrom } from "@angular/core";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import { Meta, StoryObj, applicationConfig } from "@storybook/angular";
|
||||
import { of } from "rxjs";
|
||||
import { action } from "storybook/actions";
|
||||
import { ZXCVBNResult } from "zxcvbn";
|
||||
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Story, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Story, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./registration-start.stories";
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { DOCUMENT } from "@angular/common";
|
||||
import { Injectable, Inject, NgZone, OnDestroy } from "@angular/core";
|
||||
import { Injectable, Inject, NgZone, OnDestroy, DOCUMENT } from "@angular/core";
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class AriaDisabledClickCaptureService implements OnDestroy {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Story, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Story, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./anon-layout-wrapper.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Story, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Story, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./anon-layout.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta } from "@storybook/addon-docs";
|
||||
import { Meta } from "@storybook/addon-docs/blocks";
|
||||
|
||||
<Meta title="Component Library/Async Actions/In Forms/Documentation" />
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormsModule, ReactiveFormsModule, Validators, FormBuilder } from "@angular/forms";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||
import { delay, of } from "rxjs";
|
||||
import { action } from "storybook/actions";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta } from "@storybook/addon-docs";
|
||||
import { Meta } from "@storybook/addon-docs/blocks";
|
||||
|
||||
<Meta title="Component Library/Async Actions/Overview" />
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Story } from "@storybook/addon-docs";
|
||||
import { Meta, Story } from "@storybook/addon-docs/blocks";
|
||||
import * as stories from "./standalone.stories.ts";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
import { delay, of } from "rxjs";
|
||||
import { action } from "storybook/actions";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Description, Meta, Canvas, Primary, Controls, Title } from "@storybook/addon-docs";
|
||||
import { Meta, Description, Canvas, Primary, Controls, Title } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./avatar.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./badge.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Canvas, Controls, Description, Meta, Primary, Title } from "@storybook/addon-docs";
|
||||
import { Canvas, Controls, Description, Meta, Primary, Title } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./banner.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./breadcrumbs.stories";
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Controls,
|
||||
Title,
|
||||
Description,
|
||||
} from "@storybook/addon-docs";
|
||||
} from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./button.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./callout.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Primary, Controls, Canvas, Title, Description } from "@storybook/addon-docs";
|
||||
import { Meta, Primary, Controls, Canvas, Title, Description } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./base-card.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Meta, Canvas, Source, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import {
|
||||
Meta,
|
||||
Canvas,
|
||||
Source,
|
||||
Primary,
|
||||
Controls,
|
||||
Title,
|
||||
Description,
|
||||
} from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./checkbox.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Primary, Controls, Canvas, Title, Description } from "@storybook/addon-docs";
|
||||
import { Meta, Primary, Controls, Canvas, Title, Description } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./chip-select.stories";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
import { getAllByRole, userEvent } from "@storybook/test";
|
||||
import { getAllByRole, userEvent } from "storybook/test";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./color-password.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Primary, Title, Description } from "@storybook/addon-docs";
|
||||
import { Meta, Primary, Title, Description } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./container.stories";
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { Component, inject } from "@angular/core";
|
||||
import { NoopAnimationsModule, provideAnimations } from "@angular/platform-browser/animations";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
|
||||
import { getAllByRole, userEvent } from "@storybook/test";
|
||||
import { getAllByRole, userEvent } from "storybook/test";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
@@ -36,7 +36,7 @@ interface Animal {
|
||||
imports: [ButtonModule, LayoutComponent],
|
||||
})
|
||||
class StoryDialogComponent {
|
||||
constructor(public dialogService: DialogService) {}
|
||||
dialogService = inject(DialogService);
|
||||
|
||||
openDialog() {
|
||||
this.dialogService.open(StoryDialogContentComponent, {
|
||||
@@ -85,10 +85,8 @@ class StoryDialogComponent {
|
||||
imports: [DialogModule, ButtonModule],
|
||||
})
|
||||
class StoryDialogContentComponent {
|
||||
constructor(
|
||||
public dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) private data: Animal,
|
||||
) {}
|
||||
dialogRef = inject(DialogRef);
|
||||
private data = inject<Animal>(DIALOG_DATA);
|
||||
|
||||
get animal() {
|
||||
return this.data?.animal;
|
||||
@@ -118,10 +116,8 @@ class StoryDialogContentComponent {
|
||||
imports: [DialogModule, ButtonModule],
|
||||
})
|
||||
class NonDismissableContentComponent {
|
||||
constructor(
|
||||
public dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) private data: Animal,
|
||||
) {}
|
||||
dialogRef = inject(DialogRef);
|
||||
private data = inject<Animal>(DIALOG_DATA);
|
||||
|
||||
get animal() {
|
||||
return this.data?.animal;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./dialog.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Source } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Source } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./dialog.service.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./simple-dialog.stories";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { Component, inject } from "@angular/core";
|
||||
import { provideAnimations } from "@angular/platform-browser/animations";
|
||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
import { getAllByRole, userEvent } from "@storybook/test";
|
||||
import { getAllByRole, userEvent } from "storybook/test";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
@@ -30,7 +30,7 @@ interface Animal {
|
||||
imports: [ButtonModule],
|
||||
})
|
||||
class StoryDialogComponent {
|
||||
constructor(public dialogService: DialogService) {}
|
||||
dialogService = inject(DialogService);
|
||||
|
||||
openSimpleDialog() {
|
||||
this.dialogService.open(SimpleDialogContentComponent, {
|
||||
@@ -84,10 +84,8 @@ class StoryDialogComponent {
|
||||
imports: [ButtonModule, DialogModule],
|
||||
})
|
||||
class SimpleDialogContentComponent {
|
||||
constructor(
|
||||
public dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) private data: Animal,
|
||||
) {}
|
||||
dialogRef = inject(DialogRef);
|
||||
private data = inject<Animal>(DIALOG_DATA);
|
||||
|
||||
get animal() {
|
||||
return this.data?.animal;
|
||||
@@ -115,10 +113,8 @@ class SimpleDialogContentComponent {
|
||||
imports: [ButtonModule, DialogModule],
|
||||
})
|
||||
class NonDismissableWithPrimaryButtonContentComponent {
|
||||
constructor(
|
||||
public dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) private data: Animal,
|
||||
) {}
|
||||
dialogRef = inject(DialogRef);
|
||||
private data = inject<Animal>(DIALOG_DATA);
|
||||
|
||||
get animal() {
|
||||
return this.data?.animal;
|
||||
@@ -141,10 +137,8 @@ class NonDismissableWithPrimaryButtonContentComponent {
|
||||
imports: [ButtonModule, DialogModule],
|
||||
})
|
||||
class NonDismissableWithNoButtonsContentComponent {
|
||||
constructor(
|
||||
public dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) private data: Animal,
|
||||
) {}
|
||||
dialogRef = inject(DialogRef);
|
||||
private data = inject<Animal>(DIALOG_DATA);
|
||||
|
||||
get animal() {
|
||||
return this.data?.animal;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./disclosure.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./drawer.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Source, Primary, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Source, Primary, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./form-field.stories";
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
FormGroup,
|
||||
} from "@angular/forms";
|
||||
import { NgSelectModule } from "@ng-select/ng-select";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
import { action } from "storybook/actions";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
import { userEvent, getByText } from "@storybook/test";
|
||||
import { userEvent, getByText } from "storybook/test";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Source } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Source } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as formStories from "./form.stories";
|
||||
import * as fieldStories from "../form-field/form-field.stories";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./icon-button.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Story, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Story, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./icon.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./autofocus.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta } from "@storybook/addon-docs";
|
||||
import { Meta } from "@storybook/addon-docs/blocks";
|
||||
|
||||
<Meta title="Component Library/Form/Input Directive" />
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./item.stories";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
import { userEvent } from "@storybook/test";
|
||||
import { userEvent } from "storybook/test";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Story, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import { Meta, Story, Primary, Controls, Title, Description } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./link.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./menu.stories";
|
||||
|
||||
|
||||
@@ -108,11 +108,11 @@ export class MultiSelectComponent implements OnInit, BitFormFieldControl, Contro
|
||||
/** Needs to be arrow function to retain `this` scope. */
|
||||
keyDown = (event: KeyboardEvent) => {
|
||||
const select = this.select();
|
||||
if (!select.isOpen && event.key === "Enter" && !hasModifierKey(event)) {
|
||||
if (!select.isOpen() && event.key === "Enter" && !hasModifierKey(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (select.isOpen && event.key === "Escape" && !hasModifierKey(event)) {
|
||||
if (select.isOpen() && event.key === "Escape" && !hasModifierKey(event)) {
|
||||
this.selectedItems = [];
|
||||
select.close();
|
||||
event.stopPropagation();
|
||||
@@ -198,7 +198,9 @@ export class MultiSelectComponent implements OnInit, BitFormFieldControl, Contro
|
||||
}
|
||||
set ariaDescribedBy(value: string | undefined) {
|
||||
this._ariaDescribedBy = value;
|
||||
this.select()?.searchInput.nativeElement.setAttribute("aria-describedby", value ?? "");
|
||||
this.select()
|
||||
?.searchInput()
|
||||
.nativeElement.setAttribute("aria-describedby", value ?? "");
|
||||
}
|
||||
private _ariaDescribedBy?: string;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./popover.stories";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
import { getByRole, userEvent } from "@storybook/test";
|
||||
import { getByRole, userEvent } from "storybook/test";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./progress.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Meta, Canvas, Source, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import {
|
||||
Meta,
|
||||
Canvas,
|
||||
Source,
|
||||
Primary,
|
||||
Controls,
|
||||
Title,
|
||||
Description,
|
||||
} from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./search.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Primary, Controls, Canvas } from "@storybook/addon-docs";
|
||||
import { Meta, Primary, Controls, Canvas } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./section.stories";
|
||||
|
||||
|
||||
@@ -158,7 +158,9 @@ export class SelectComponent<T> implements BitFormFieldControl, ControlValueAcce
|
||||
}
|
||||
set ariaDescribedBy(value: string | undefined) {
|
||||
this._ariaDescribedBy = value;
|
||||
this.select()?.searchInput.nativeElement.setAttribute("aria-describedby", value ?? "");
|
||||
this.select()
|
||||
?.searchInput()
|
||||
.nativeElement.setAttribute("aria-describedby", value ?? "");
|
||||
}
|
||||
private _ariaDescribedBy?: string;
|
||||
|
||||
@@ -218,7 +220,7 @@ export class SelectComponent<T> implements BitFormFieldControl, ControlValueAcce
|
||||
* Needs to be arrow function to retain `this` scope.
|
||||
*/
|
||||
protected onKeyDown = (event: KeyboardEvent) => {
|
||||
if (this.select().isOpen && event.key === "Escape" && !hasModifierKey(event)) {
|
||||
if (this.select().isOpen() && event.key === "Escape" && !hasModifierKey(event)) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Source, Primary, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Source, Primary, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./select.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Source } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Source } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as skeletonStories from "./skeleton.stories";
|
||||
import * as skeletonTextStories from "./skeleton-text.stories";
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Meta, Story, Source, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import {
|
||||
Meta,
|
||||
Story,
|
||||
Source,
|
||||
Primary,
|
||||
Controls,
|
||||
Title,
|
||||
Description,
|
||||
} from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./stepper.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta } from "@storybook/addon-docs";
|
||||
import { Meta } from "@storybook/addon-docs/blocks";
|
||||
|
||||
<Meta title="Documentation/Colors" />
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Story, Canvas } from "@storybook/addon-docs";
|
||||
import { Meta, Story, Canvas } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as itemStories from "../item/item.stories";
|
||||
import * as popupLayoutStories from "../../../../apps/browser/src/platform/popup/layout/popup-layout.stories";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./icons.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta } from "@storybook/addon-docs";
|
||||
import { Meta } from "@storybook/addon-docs/blocks";
|
||||
|
||||
<Meta title="Documentation/Introduction" />
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Story } from "@storybook/addon-docs";
|
||||
import { Meta, Story } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./kitchen-sink.stories";
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
fireEvent,
|
||||
getByText,
|
||||
getAllByLabelText,
|
||||
} from "@storybook/test";
|
||||
} from "storybook/test";
|
||||
|
||||
import { PasswordManagerLogo } from "@bitwarden/assets/svg";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta } from "@storybook/addon-docs";
|
||||
import { Meta } from "@storybook/addon-docs/blocks";
|
||||
|
||||
<Meta title="Documentation/Migration" />
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta } from "@storybook/addon-docs";
|
||||
import { Meta } from "@storybook/addon-docs/blocks";
|
||||
|
||||
<Meta title="Documentation/Responsive Design" />
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta } from "@storybook/addon-docs";
|
||||
import { Meta } from "@storybook/addon-docs/blocks";
|
||||
|
||||
<Meta title="Documentation/Virtual Scrolling" />
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Meta, Canvas, Source, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import {
|
||||
Meta,
|
||||
Canvas,
|
||||
Source,
|
||||
Primary,
|
||||
Controls,
|
||||
Title,
|
||||
Description,
|
||||
} from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./switch.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Story, Source, Primary, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Story, Source, Primary, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./table.stories";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs";
|
||||
import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./tabs.stories";
|
||||
import * as dialogStories from "../dialog/dialog/dialog.stories";
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Meta, Canvas, Source, Primary, Controls, Title, Description } from "@storybook/addon-docs";
|
||||
import {
|
||||
Meta,
|
||||
Canvas,
|
||||
Source,
|
||||
Primary,
|
||||
Controls,
|
||||
Title,
|
||||
Description,
|
||||
} from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./toast.stories";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
|
||||
import { action } from "storybook/actions";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, Story, Primary, Controls, Canvas } from "@storybook/addon-docs";
|
||||
import { Meta, Story, Primary, Controls, Canvas } from "@storybook/addon-docs/blocks";
|
||||
|
||||
import * as stories from "./toggle-group.stories";
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user