From 48a0a16050f1131b10cd431619dc2bb9995a3aea Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Fri, 8 Mar 2024 14:41:32 -0500 Subject: [PATCH 01/22] Set up Transfer modal --- web/app/components/document/modal.ts | 2 +- web/app/components/document/sidebar.hbs | 66 ++++++++++++++++ web/app/components/document/sidebar.ts | 83 +++++++++++++++++++++ web/app/components/inputs/people-select.hbs | 6 +- web/app/components/inputs/people-select.ts | 52 +++++++------ web/app/styles/components/multiselect.scss | 8 ++ web/app/styles/typography.scss | 7 +- 7 files changed, 197 insertions(+), 27 deletions(-) diff --git a/web/app/components/document/modal.ts b/web/app/components/document/modal.ts index 213c22f1e..0aab33981 100644 --- a/web/app/components/document/modal.ts +++ b/web/app/components/document/modal.ts @@ -18,7 +18,7 @@ interface DocumentModalComponentSignature { hideFooterWhileSaving?: boolean; color?: HdsModalColor; close: () => void; - task?: () => Promise | void; + task?: (newOwner?: string) => Promise | void; }; Blocks: { default: [{ taskIsRunning: boolean }]; diff --git a/web/app/components/document/sidebar.hbs b/web/app/components/document/sidebar.hbs index a7398ed4c..788607554 100644 --- a/web/app/components/document/sidebar.hbs +++ b/web/app/components/document/sidebar.hbs @@ -406,7 +406,23 @@ {{/if}} {{/each-in}} + + {{! Transfer ownership }} + {{#if (and this.isOwner (not this.isDraft))}} +
+
+ + + Transfer ownership... + +
+
+ {{/if}} + {{#if this.footerIsShown}} @@ -605,6 +621,56 @@ /> {{/if}} + {{#if this.transferOwnershipModalIsShown}} + + <:default as |M|> +

+ Transfer this document to someone else in your workspace. +
+ We'll email the new owner when the transfer is complete. +

+ {{! TODO: semantics }} +
+ Select new owner +
+ + {{! Errors? }} + + {{! Type to confirm? }} +
+ Type + TRANSFER + to confirm +
+ {{! EXCLUDE self from results }} + + +
+ {{/if}} + {{#if this.requestReviewModalIsShown}} { + // assert the newOwner exists + try { + await this.patchDocument.perform({ + owners: [newOwner], + }); + + this.transferOwnershipModalIsShown = false; + + this.flashMessages.add({ + message: "Ownership transferred", + // TODO: more explanation? + title: "Done!", + }); + } catch (error) { + const e = error as Error; + this.maybeLockDoc(e); + this.showFlashError(e, "Error transferring ownership"); + } + }); + @action updateApprovers(approvers: string[]) { this.approvers = approvers; } diff --git a/web/app/components/inputs/people-select.hbs b/web/app/components/inputs/people-select.hbs index 7a685266f..c3357be72 100644 --- a/web/app/components/inputs/people-select.hbs +++ b/web/app/components/inputs/people-select.hbs @@ -1,6 +1,6 @@ diff --git a/web/app/components/inputs/people-select.ts b/web/app/components/inputs/people-select.ts index 67ae69c4a..1c6cc2be6 100644 --- a/web/app/components/inputs/people-select.ts +++ b/web/app/components/inputs/people-select.ts @@ -9,7 +9,7 @@ import Ember from "ember"; import StoreService from "hermes/services/store"; import PersonModel from "hermes/models/person"; import { Select } from "ember-power-select/components/power-select"; -import { next } from "@ember/runloop"; +import { next, schedule } from "@ember/runloop"; import calculatePosition from "ember-basic-dropdown/utils/calculate-position"; import { assert } from "@ember/debug"; @@ -47,6 +47,12 @@ interface InputsPeopleSelectComponentSignature { renderInPlace?: boolean; disabled?: boolean; onKeydown?: (dropdown: any, event: KeyboardEvent) => void; + /** + * Whether the dropdown should be single-select. + * When true, will not show the dropdown when there's a selection. + */ + isSingleSelect?: boolean; + destination?: string; }; } @@ -129,31 +135,31 @@ export default class InputsPeopleSelectComponent extends Component Date: Mon, 11 Mar 2024 14:26:36 -0400 Subject: [PATCH 02/22] Set up TypeToConfirm --- web/app/components/document/modal.hbs | 16 ++-- web/app/components/document/modal.ts | 1 + web/app/components/document/sidebar.hbs | 89 ++++++++++---------- web/app/components/document/sidebar.ts | 58 ++++++++----- web/app/components/inputs/people-select.hbs | 1 + web/app/components/inputs/people-select.ts | 4 +- web/app/components/type-to-confirm.hbs | 12 +++ web/app/components/type-to-confirm.ts | 45 ++++++++++ web/app/components/type-to-confirm/input.hbs | 14 +++ web/app/components/type-to-confirm/input.ts | 22 +++++ web/types/hds/form/text-input/base.d.ts | 31 ++++--- 11 files changed, 204 insertions(+), 89 deletions(-) create mode 100644 web/app/components/type-to-confirm.hbs create mode 100644 web/app/components/type-to-confirm.ts create mode 100644 web/app/components/type-to-confirm/input.hbs create mode 100644 web/app/components/type-to-confirm/input.ts diff --git a/web/app/components/document/modal.hbs b/web/app/components/document/modal.hbs index 2ab808351..72ce6e910 100644 --- a/web/app/components/document/modal.hbs +++ b/web/app/components/document/modal.hbs @@ -35,13 +35,15 @@ disabled={{or @taskButtonIsDisabled this.taskIsRunning}} {{on "click" (perform this.task)}} /> - + {{#unless @secondaryButtonIsHidden}} + + {{/unless}} {{/if}} diff --git a/web/app/components/document/modal.ts b/web/app/components/document/modal.ts index 0aab33981..214bb34af 100644 --- a/web/app/components/document/modal.ts +++ b/web/app/components/document/modal.ts @@ -17,6 +17,7 @@ interface DocumentModalComponentSignature { taskButtonIsDisabled?: boolean; hideFooterWhileSaving?: boolean; color?: HdsModalColor; + secondaryButtonIsHidden?: boolean; close: () => void; task?: (newOwner?: string) => Promise | void; }; diff --git a/web/app/components/document/sidebar.hbs b/web/app/components/document/sidebar.hbs index 788607554..ad058b1f1 100644 --- a/web/app/components/document/sidebar.hbs +++ b/web/app/components/document/sidebar.hbs @@ -622,53 +622,50 @@ {{/if}} {{#if this.transferOwnershipModalIsShown}} - - <:default as |M|> -

- Transfer this document to someone else in your workspace. -
- We'll email the new owner when the transfer is complete. -

- {{! TODO: semantics }} -
- Select new owner -
- - {{! Errors? }} + + + <:default> +

+ Give this document to someone in your workspace. +
+ We'll notify them when the transfer completes. +

+ + + + + +
+
- {{! Type to confirm? }} -
- Type - TRANSFER - to confirm -
- {{! EXCLUDE self from results }} - - -
{{/if}} {{#if this.requestReviewModalIsShown}} diff --git a/web/app/components/document/sidebar.ts b/web/app/components/document/sidebar.ts index 590e859f4..f893ec248 100644 --- a/web/app/components/document/sidebar.ts +++ b/web/app/components/document/sidebar.ts @@ -72,6 +72,8 @@ export default class DocumentSidebarComponent extends Component; + hasConfirmed: boolean; +} + +interface TypeToConfirmSignature { + Args: { + value: string; + }; + Blocks: { + default: [T: TypeToConfirmInterface]; + }; +} + +export default class TypeToConfirm extends Component { + @tracked protected inputValue = ""; + @tracked protected hasConfirmed = false; + + protected get id() { + return guidFor(this); + } + + @action protected updateValue(event: Event) { + this.inputValue = (event.target as HTMLInputElement).value; + + if (this.inputValue === this.args.value) { + this.hasConfirmed = true; + } + } +} + +declare module "@glint/environment-ember-loose/registry" { + export default interface Registry { + TypeToConfirm: typeof TypeToConfirm; + } +} diff --git a/web/app/components/type-to-confirm/input.hbs b/web/app/components/type-to-confirm/input.hbs new file mode 100644 index 000000000..4b96a72ab --- /dev/null +++ b/web/app/components/type-to-confirm/input.hbs @@ -0,0 +1,14 @@ + + + diff --git a/web/app/components/type-to-confirm/input.ts b/web/app/components/type-to-confirm/input.ts new file mode 100644 index 000000000..22765d09e --- /dev/null +++ b/web/app/components/type-to-confirm/input.ts @@ -0,0 +1,22 @@ +import Component from "@glimmer/component"; + +interface TypeToConfirmInputSignature { + Element: HTMLInputElement; + Args: { + onInput: (event: Event) => void; + inputValue: string; + value: string; + id: string; + }; + Blocks: { + default: []; + }; +} + +export default class TypeToConfirmInput extends Component {} + +declare module "@glint/environment-ember-loose/registry" { + export default interface Registry { + "type-to-confirm/input": typeof TypeToConfirmInput; + } +} diff --git a/web/types/hds/form/text-input/base.d.ts b/web/types/hds/form/text-input/base.d.ts index eac9e8f19..4156d47f1 100644 --- a/web/types/hds/form/text-input/base.d.ts +++ b/web/types/hds/form/text-input/base.d.ts @@ -1,17 +1,22 @@ // https://helios.hashicorp.design/components/form/text-input?tab=code#formtextinputbase-1 -import { ComponentLike } from "@glint/template"; -import { HdsFormTextInputArgs } from "."; +declare module "@hashicorp/design-system-components/components/hds/form/text-input/base" { + import Component from "@glimmer/component"; + import { ComponentLike } from "@glint/template"; + import { HdsFormTextInputArgs } from "."; -export interface HdsFormTextInputBaseComponentSignature { - Element: HTMLInputElement; - Args: { - type?: string; - value: string | number | Date; - isInvalid?: boolean; - width?: string; - }; -} + export interface HdsFormTextInputBaseComponentSignature { + Element: HTMLInputElement; + Args: { + type?: string; + value: string | number | Date; + isInvalid?: boolean; + width?: string; + }; + } + + export type HdsFormTextInputBaseComponent = + ComponentLike; -export type HdsFormTextInputBaseComponent = - ComponentLike; + export default class HdsFormTextInputBase extends Component {} +} From 2b1bca2e9de7541d314c0d5f48754de508e76de9 Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Mon, 11 Mar 2024 14:36:26 -0400 Subject: [PATCH 03/22] Improve design --- web/app/components/document/sidebar.hbs | 42 +++++++++++--------- web/app/components/type-to-confirm.ts | 2 + web/app/components/type-to-confirm/input.hbs | 1 + 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/web/app/components/document/sidebar.hbs b/web/app/components/document/sidebar.hbs index ad058b1f1..33e5f4b08 100644 --- a/web/app/components/document/sidebar.hbs +++ b/web/app/components/document/sidebar.hbs @@ -622,7 +622,7 @@ {{/if}} {{#if this.transferOwnershipModalIsShown}} - + - - - +
+
+ + +
+
+ +
+
diff --git a/web/app/components/type-to-confirm.ts b/web/app/components/type-to-confirm.ts index e653a63c9..23ea6120e 100644 --- a/web/app/components/type-to-confirm.ts +++ b/web/app/components/type-to-confirm.ts @@ -34,6 +34,8 @@ export default class TypeToConfirm extends Component { if (this.inputValue === this.args.value) { this.hasConfirmed = true; + } else { + this.hasConfirmed = false; } } } diff --git a/web/app/components/type-to-confirm/input.hbs b/web/app/components/type-to-confirm/input.hbs index 4b96a72ab..0928e88a9 100644 --- a/web/app/components/type-to-confirm/input.hbs +++ b/web/app/components/type-to-confirm/input.hbs @@ -9,6 +9,7 @@ From 99b38d7ad26d5286e717d1c7d3f36cebdc12c817 Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Mon, 11 Mar 2024 15:34:36 -0400 Subject: [PATCH 04/22] Improve error handling --- web/app/components/document/modal.ts | 5 ++ web/app/components/document/sidebar.hbs | 67 +++++++++++--------- web/app/components/document/sidebar.ts | 34 ++++++++-- web/app/components/type-to-confirm.hbs | 1 + web/app/components/type-to-confirm.ts | 16 ++++- web/app/components/type-to-confirm/input.hbs | 1 + web/app/components/type-to-confirm/input.ts | 1 + 7 files changed, 87 insertions(+), 38 deletions(-) diff --git a/web/app/components/document/modal.ts b/web/app/components/document/modal.ts index 214bb34af..86694745e 100644 --- a/web/app/components/document/modal.ts +++ b/web/app/components/document/modal.ts @@ -70,6 +70,11 @@ export default class DocumentModalComponent extends Component + - <:default> -

- Give this document to someone in your workspace. -
- We'll notify them when the transfer completes. -

- -
-
- - + <:default as |M|> + {{#if M.taskIsRunning}} +
+ +

Transferring doc...

-
- + {{else}} + +

+ Give this document to someone in your workspace. +
+ We'll notify them when the transfer completes. +

+ +
+
+ + +
+
+ +
-
+ {{/if}} diff --git a/web/app/components/document/sidebar.ts b/web/app/components/document/sidebar.ts index f893ec248..5d256e7a9 100644 --- a/web/app/components/document/sidebar.ts +++ b/web/app/components/document/sidebar.ts @@ -600,6 +600,21 @@ export default class DocumentSidebarComponent extends Component { + patchDocument = enqueueTask(async (fields: any, throwOnError?: boolean) => { const endpoint = this.isDraft ? "drafts" : "documents"; try { @@ -784,6 +799,9 @@ export default class DocumentSidebarComponent extends Component { - // assert the newOwner exists try { - await this.patchDocument.perform({ - owners: [newOwner], - }); + await this.patchDocument.perform( + { + owners: [newOwner], + }, + true, + ); this.transferOwnershipModalIsShown = false; this.flashMessages.add({ message: "Ownership transferred", - // TODO: more explanation? title: "Done!", }); } catch (error) { const e = error as Error; this.maybeLockDoc(e); - this.showFlashError(e, "Error transferring ownership"); + throw e; + // show modal error } }); diff --git a/web/app/components/type-to-confirm.hbs b/web/app/components/type-to-confirm.hbs index 60af0590e..a2325a29f 100644 --- a/web/app/components/type-to-confirm.hbs +++ b/web/app/components/type-to-confirm.hbs @@ -6,6 +6,7 @@ value=@value inputValue=this.inputValue onInput=this.updateValue + onKeydown=this.onKeydown ) hasConfirmed=this.hasConfirmed ) diff --git a/web/app/components/type-to-confirm.ts b/web/app/components/type-to-confirm.ts index 23ea6120e..8a085dabe 100644 --- a/web/app/components/type-to-confirm.ts +++ b/web/app/components/type-to-confirm.ts @@ -5,7 +5,12 @@ import { tracked } from "@glimmer/tracking"; import { action } from "@ember/object"; import TypeToConfirmInput from "./type-to-confirm/input"; -type TypeToConfirmInputBoundArgs = "value" | "id" | "inputValue" | "onInput"; +type TypeToConfirmInputBoundArgs = + | "value" + | "id" + | "inputValue" + | "onInput" + | "onKeydown"; interface TypeToConfirmInterface { Input: WithBoundArgs; @@ -15,6 +20,7 @@ interface TypeToConfirmInterface { interface TypeToConfirmSignature { Args: { value: string; + onEnter?: () => void; }; Blocks: { default: [T: TypeToConfirmInterface]; @@ -38,6 +44,14 @@ export default class TypeToConfirm extends Component { this.hasConfirmed = false; } } + + @action onKeydown(event: KeyboardEvent) { + const { onEnter } = this.args; + + if (this.hasConfirmed && event.key === "Enter" && onEnter) { + onEnter(); + } + } } declare module "@glint/environment-ember-loose/registry" { diff --git a/web/app/components/type-to-confirm/input.hbs b/web/app/components/type-to-confirm/input.hbs index 0928e88a9..56d1e62e0 100644 --- a/web/app/components/type-to-confirm/input.hbs +++ b/web/app/components/type-to-confirm/input.hbs @@ -8,6 +8,7 @@ void; + onKeydown: (event: KeyboardEvent) => void; inputValue: string; value: string; id: string; From 6d6f22d8c4f10a86f2d1f60801cdbd92ef83e494 Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Mon, 11 Mar 2024 15:36:59 -0400 Subject: [PATCH 05/22] Tweak TypeToConfirm value --- web/app/components/document/sidebar.hbs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/app/components/document/sidebar.hbs b/web/app/components/document/sidebar.hbs index 3b9aa135b..5aa16c790 100644 --- a/web/app/components/document/sidebar.hbs +++ b/web/app/components/document/sidebar.hbs @@ -622,7 +622,11 @@ {{/if}} {{#if this.transferOwnershipModalIsShown}} - + Date: Mon, 11 Mar 2024 16:43:09 -0400 Subject: [PATCH 06/22] Write and stub tests --- web/app/components/document/sidebar.hbs | 7 +- web/app/components/document/sidebar.ts | 31 ++++---- web/app/components/inputs/people-select.hbs | 4 +- web/app/components/inputs/people-select.ts | 40 +++++++++- web/app/components/type-to-confirm/input.hbs | 1 - web/app/components/type-to-confirm/input.ts | 1 - .../acceptance/authenticated/document-test.ts | 67 +++++++++++++++- .../components/document/modal-test.ts | 76 ++++++++++++++----- .../components/inputs/people-select-test.ts | 6 ++ .../components/type-to-confirm-test.ts | 20 +++++ 10 files changed, 211 insertions(+), 42 deletions(-) create mode 100644 web/tests/integration/components/type-to-confirm-test.ts diff --git a/web/app/components/document/sidebar.hbs b/web/app/components/document/sidebar.hbs index 5aa16c790..957fb1174 100644 --- a/web/app/components/document/sidebar.hbs +++ b/web/app/components/document/sidebar.hbs @@ -412,6 +412,7 @@
@@ -629,8 +630,9 @@ > <:default as |M|> {{#if M.taskIsRunning}} @@ -660,6 +661,7 @@
diff --git a/web/app/components/document/sidebar.ts b/web/app/components/document/sidebar.ts index 5d256e7a9..bb9b13dbf 100644 --- a/web/app/components/document/sidebar.ts +++ b/web/app/components/document/sidebar.ts @@ -72,8 +72,6 @@ export default class DocumentSidebarComponent extends Component { @service("config") declare configSvc: ConfigService; @service("fetch") declare fetchSvc: FetchService; + @service declare authenticatedUser: AuthenticatedUserService; @service declare store: StoreService; /** @@ -86,6 +88,25 @@ export default class InputsPeopleSelectComponent extends Component { + dropdown.actions.close(); + }); + break; + default: + if (this.args.onKeydown) { + this.args.onKeydown(dropdown, event); + } + } + } + /** * An action occurring on every keystroke. * Handles cases where the user clears the input, @@ -113,6 +134,14 @@ export default class InputsPeopleSelectComponent extends Component 0) { + select.actions.close(); + } + } /** * Custom position-calculating function for the dropdown. @@ -184,6 +213,13 @@ export default class InputsPeopleSelectComponent extends Component selectedEmail === email, ); + }) + .filter((email: string) => { + // filter the authenticated user if `excludeSelf` is true + return ( + !this.args.excludeSelf || + email !== this.authenticatedUser.info.email + ); }); } else { this.people = []; diff --git a/web/app/components/type-to-confirm/input.hbs b/web/app/components/type-to-confirm/input.hbs index 56d1e62e0..6c4751e5a 100644 --- a/web/app/components/type-to-confirm/input.hbs +++ b/web/app/components/type-to-confirm/input.hbs @@ -12,5 +12,4 @@ @value={{@inputValue}} class="mt-1" id={{@id}} - ...attributes /> diff --git a/web/app/components/type-to-confirm/input.ts b/web/app/components/type-to-confirm/input.ts index ae0476b58..aea8cc289 100644 --- a/web/app/components/type-to-confirm/input.ts +++ b/web/app/components/type-to-confirm/input.ts @@ -1,7 +1,6 @@ import Component from "@glimmer/component"; interface TypeToConfirmInputSignature { - Element: HTMLInputElement; Args: { onInput: (event: Event) => void; onKeydown: (event: KeyboardEvent) => void; diff --git a/web/tests/acceptance/authenticated/document-test.ts b/web/tests/acceptance/authenticated/document-test.ts index 1d90ee75a..e380fb27d 100644 --- a/web/tests/acceptance/authenticated/document-test.ts +++ b/web/tests/acceptance/authenticated/document-test.ts @@ -63,9 +63,13 @@ const PRODUCT_SELECT_PRODUCT_NAME = "[data-test-product-value]"; const POPOVER = "[data-test-x-dropdown-list-content]"; const PRODUCT_SELECT_DROPDOWN_ITEM = `${POPOVER} [data-test-product-select-item]`; const TOGGLE_SELECT = "[data-test-x-dropdown-list-toggle-select]"; +// Transfer Ownership +const TRANSFER_OWNERSHIP_BUTTON = + "[data-test-transfer-document-ownership-button]"; +const TRANSFER_OWNERSHIP_MODAL = + "[data-test-transfer-document-ownership-modal]"; const DISABLED_FOOTER_H5 = "[data-test-disabled-footer-h5]"; - const OWNER_LINK = "[data-test-owner-link]"; const EDITABLE_FIELD_READ_VALUE = "[data-test-editable-field-read-value]"; @@ -695,6 +699,19 @@ module("Acceptance | authenticated/document", function (hooks) { .hasText("In review", "the status is shown but not as a toggle"); }); + test("non-owners don't see the transfer-ownership button", async function (this: AuthenticatedDocumentRouteTestContext, assert) { + this.server.create("document", { + objectID: 1, + isDraft: false, + status: "In-Review", + owners: [TEST_USER_2_EMAIL], + }); + + await visit("/document/1"); + + assert.dom(TRANSFER_OWNERSHIP_BUTTON).doesNotExist(); + }); + test("doc owners can publish their docs for review", async function (this: AuthenticatedDocumentRouteTestContext, assert) { this.server.create("document", { objectID: 1, @@ -1569,4 +1586,52 @@ module("Acceptance | authenticated/document", function (hooks) { assert.dom(DISABLED_FOOTER_H5).hasText("Document is locked"); }); + + test("owners can transfer ownership of their published docs", async function (this: AuthenticatedDocumentRouteTestContext, assert) { + this.server.create("document", { + objectID: 1, + }); + + await visit("/document/1"); + + assert + .dom(TRANSFER_OWNERSHIP_BUTTON) + .doesNotExist("drafts cannot be transferred"); + + const doc = this.server.schema.document.first(); + + doc.update({ + isDraft: false, + status: "In review", + }); + + assert + .dom(TRANSFER_OWNERSHIP_BUTTON) + .exists('the "transfer ownership" button is shown for published docs'); + + await click(TRANSFER_OWNERSHIP_BUTTON); + + assert.dom(TRANSFER_OWNERSHIP_MODAL).exists(); + + // assert button is disabled + // fill out people select + // assert button is still disabled + // typeToConfirm + // assert button is enabled + // click button + // assert "Transferring doc..." state is shown + // assert clicking the label focuses the input + // assert that when you select someone in the people select, the next input is focused + // assert that keying enter doesn't open a "locked" peopleselect + // assert single-selectness is enforced + // assert you can't select yourself + // assert that transferring works + // assert that the modal closes + // assert that a flash message is shown + // assert that the back-end is updated + }); + + test("an error is shown when ownership transferring fails", async function (this: AuthenticatedDocumentRouteTestContext, assert) { + // assert error states + }); }); diff --git a/web/tests/integration/components/document/modal-test.ts b/web/tests/integration/components/document/modal-test.ts index 2225ac248..97804e5c1 100644 --- a/web/tests/integration/components/document/modal-test.ts +++ b/web/tests/integration/components/document/modal-test.ts @@ -11,6 +11,10 @@ import { import { hbs } from "ember-cli-htmlbars"; import { assert as emberAssert } from "@ember/debug"; +const PRIMARY_BUTTON = "[data-test-document-modal-primary-button]"; +const SECONDARY_BUTTON = "[data-test-document-modal-secondary-button]"; +const ERROR = ".hds-alert"; + interface DocumentModalTestContext extends TestContext { color?: string; headerText: string; @@ -68,10 +72,10 @@ module("Integration | Component | document/modal", function (hooks) { .dom(".hds-modal__body") .hasText( "Are you sure you want to archive this document?", - "can take a @bodyText argument" + "can take a @bodyText argument", ); - const primaryButton = find("[data-test-document-modal-primary-button]"); + const primaryButton = find(PRIMARY_BUTTON); emberAssert("primary button must exist", primaryButton); @@ -86,10 +90,10 @@ module("Integration | Component | document/modal", function (hooks) { .hasAttribute( "data-test-icon", "archive", - "can take a @taskButtonIcon argument" + "can take a @taskButtonIcon argument", ); - assert.dom(".hds-alert").doesNotExist("error is not shown by default"); + assert.dom(ERROR).doesNotExist("error is not shown by default"); this.set("task", async () => { throw new Error("error"); @@ -97,12 +101,12 @@ module("Integration | Component | document/modal", function (hooks) { await click(primaryButton); - assert.dom(".hds-alert").exists("failed tasks show an error"); - assert.dom(".hds-alert .hds-alert__title").hasText("Error title"); + assert.dom(ERROR).exists("failed tasks show an error"); + assert.dom(`${ERROR} .hds-alert__title`).hasText("Error title"); await click(".hds-alert__dismiss"); - assert.dom(".hds-alert").doesNotExist("error can be dismissed"); + assert.dom(ERROR).doesNotExist("error can be dismissed"); }); test("it yields a body block with a taskIsRunning property", async function (assert) { @@ -134,7 +138,7 @@ module("Integration | Component | document/modal", function (hooks) { assert.dom("[data-test-body-block]").hasText("idle"); - const clickPromise = click("[data-test-document-modal-primary-button]"); + const clickPromise = click(PRIMARY_BUTTON); await waitFor("[data-test-body-block] span"); @@ -159,16 +163,15 @@ module("Integration | Component | document/modal", function (hooks) { /> `); - const buttonSelector = "[data-test-document-modal-primary-button]"; - const iconSelector = buttonSelector + " .flight-icon"; + const iconSelector = PRIMARY_BUTTON + " .flight-icon"; - assert.dom(buttonSelector).hasText("Yes, archive"); + assert.dom(PRIMARY_BUTTON).hasText("Yes, archive"); assert.dom(iconSelector).hasAttribute("data-test-icon", "archive"); - const clickPromise = click(buttonSelector); + const clickPromise = click(PRIMARY_BUTTON); await waitUntil(() => { - return find(buttonSelector)?.textContent?.trim() === "Archiving..."; + return find(PRIMARY_BUTTON)?.textContent?.trim() === "Archiving..."; }); assert.dom(iconSelector).hasAttribute("data-test-icon", "loading"); @@ -192,9 +195,7 @@ module("Integration | Component | document/modal", function (hooks) { /> `); - assert - .dom("[data-test-document-modal-primary-button]") - .hasAttribute("disabled"); + assert.dom(PRIMARY_BUTTON).hasAttribute("disabled"); }); test("the close action runs when the modal is dismissed", async function (assert) { @@ -215,7 +216,7 @@ module("Integration | Component | document/modal", function (hooks) { /> `); - await click("[data-test-document-modal-secondary-button]"); + await click(SECONDARY_BUTTON); await waitUntil(() => count === 1); assert.equal(count, 1); @@ -236,7 +237,7 @@ module("Integration | Component | document/modal", function (hooks) { assert.dom("[data-test-document-modal-footer]").exists(); - const clickPromise = click("[data-test-document-modal-primary-button]"); + const clickPromise = click(PRIMARY_BUTTON); await waitUntil(() => { return find("[data-test-document-modal-footer]") === null; @@ -246,4 +247,43 @@ module("Integration | Component | document/modal", function (hooks) { await clickPromise; }); + + test("the secondary button can be hidden", async function (assert) { + await render(hbs` + + `); + + assert.dom(SECONDARY_BUTTON).doesNotExist(); + }); + + test("error are not shown when a full-modal task is running", async function (assert) { + this.set("task", async () => { + throw new Error("error"); + }); + + await render(hbs` + + `); + + await click(PRIMARY_BUTTON); + + assert.dom(ERROR).exists(); + + const clickPromise = click(PRIMARY_BUTTON); + + await waitUntil(() => { + return find(ERROR) === null; + }); + + await clickPromise; + }); }); diff --git a/web/tests/integration/components/inputs/people-select-test.ts b/web/tests/integration/components/inputs/people-select-test.ts index 665331116..9e60abd91 100644 --- a/web/tests/integration/components/inputs/people-select-test.ts +++ b/web/tests/integration/components/inputs/people-select-test.ts @@ -122,4 +122,10 @@ module("Integration | Component | inputs/people-select", function (hooks) { .dom(".ember-power-select-option") .exists({ count: 5 }, "Returns results after retrying"); }); + + test("you can exclude the authenticated user from the list", async function (this: PeopleSelectContext, assert) { + // test the `excludeSelf` argument + }); + + test("you can limit the selection to a single person", async function (this: PeopleSelectContext, assert) {}); }); diff --git a/web/tests/integration/components/type-to-confirm-test.ts b/web/tests/integration/components/type-to-confirm-test.ts new file mode 100644 index 000000000..5b92a93a1 --- /dev/null +++ b/web/tests/integration/components/type-to-confirm-test.ts @@ -0,0 +1,20 @@ +import { module, test } from "qunit"; +import { setupRenderingTest } from "ember-qunit"; +import { TestContext, render } from "@ember/test-helpers"; +import { hbs } from "ember-cli-htmlbars"; + +interface Context extends TestContext {} + +module("Integration | Component | type-to-confirm", function (hooks) { + setupRenderingTest(hooks); + + test("it yields an input component", async function (this: Context, assert) {}); + + test("it yields a `hasConfirmed` value", async function (this: Context, assert) {}); + + test("it generates an id", async function (this: Context, assert) {}); + + test("the `hasConfirmed` value is `true` when the input value matches the passed-in value", async function (this: Context, assert) {}); + + test("in the `hasConfirmed` state, keying Enter runs the passed-in `onEnter` action ", async function (this: Context, assert) {}); +}); From 5ee787dca9241337998df44d0fb3680d5565ce07 Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Mon, 11 Mar 2024 16:44:04 -0400 Subject: [PATCH 07/22] Update base.d.ts --- web/types/hds/form/text-input/base.d.ts | 31 +++++++++++-------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/web/types/hds/form/text-input/base.d.ts b/web/types/hds/form/text-input/base.d.ts index 4156d47f1..eac9e8f19 100644 --- a/web/types/hds/form/text-input/base.d.ts +++ b/web/types/hds/form/text-input/base.d.ts @@ -1,22 +1,17 @@ // https://helios.hashicorp.design/components/form/text-input?tab=code#formtextinputbase-1 -declare module "@hashicorp/design-system-components/components/hds/form/text-input/base" { - import Component from "@glimmer/component"; - import { ComponentLike } from "@glint/template"; - import { HdsFormTextInputArgs } from "."; +import { ComponentLike } from "@glint/template"; +import { HdsFormTextInputArgs } from "."; - export interface HdsFormTextInputBaseComponentSignature { - Element: HTMLInputElement; - Args: { - type?: string; - value: string | number | Date; - isInvalid?: boolean; - width?: string; - }; - } - - export type HdsFormTextInputBaseComponent = - ComponentLike; - - export default class HdsFormTextInputBase extends Component {} +export interface HdsFormTextInputBaseComponentSignature { + Element: HTMLInputElement; + Args: { + type?: string; + value: string | number | Date; + isInvalid?: boolean; + width?: string; + }; } + +export type HdsFormTextInputBaseComponent = + ComponentLike; From 88af26519560029e4f1617b0943f55be7435e17b Mon Sep 17 00:00:00 2001 From: Jeff Daley Date: Mon, 11 Mar 2024 16:46:25 -0400 Subject: [PATCH 08/22] Update sidebar.ts --- web/app/components/document/sidebar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/document/sidebar.ts b/web/app/components/document/sidebar.ts index bb9b13dbf..3fdf90909 100644 --- a/web/app/components/document/sidebar.ts +++ b/web/app/components/document/sidebar.ts @@ -573,7 +573,7 @@ export default class DocumentSidebarComponent extends Component Date: Mon, 11 Mar 2024 17:02:09 -0400 Subject: [PATCH 09/22] Write type-to-confirm tests --- web/app/components/type-to-confirm/input.hbs | 3 +- .../components/type-to-confirm-test.ts | 91 +++++++++++++++++-- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/web/app/components/type-to-confirm/input.hbs b/web/app/components/type-to-confirm/input.hbs index 6c4751e5a..71abfe07d 100644 --- a/web/app/components/type-to-confirm/input.hbs +++ b/web/app/components/type-to-confirm/input.hbs @@ -1,4 +1,4 @@ -