diff --git a/web/app/components/dashboard/docs-awaiting-review.hbs b/web/app/components/dashboard/docs-awaiting-review.hbs new file mode 100644 index 000000000..23b8055a9 --- /dev/null +++ b/web/app/components/dashboard/docs-awaiting-review.hbs @@ -0,0 +1,36 @@ +
+
+

+ You have + + document{{if (gt @docs.length 1) "s"}} + awaiting your review: +

+ +
+ + + {{#if this.toggleButtonIsShown}} + + + {{if this.isCollapsed "Show all" "Show fewer"}} + + {{/if}} +
diff --git a/web/app/components/dashboard/docs-awaiting-review.ts b/web/app/components/dashboard/docs-awaiting-review.ts new file mode 100644 index 000000000..4f6901c77 --- /dev/null +++ b/web/app/components/dashboard/docs-awaiting-review.ts @@ -0,0 +1,58 @@ +import { action } from "@ember/object"; +import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; +import { HermesDocument } from "hermes/types/document"; + +interface DashboardDocsAwaitingReviewComponentSignature { + Element: null; + Args: { + docs: HermesDocument[]; + }; + Blocks: { + default: []; + }; +} + +export default class DashboardDocsAwaitingReviewComponent extends Component { + /** + * Whether the list is considered collapsed. Determines which docs to show, + * as well as the text and icon of the toggle button. + */ + @tracked protected isCollapsed = true; + + /** + * (Up to) the first four docs. The default documents shown. + */ + private get firstFourDocs() { + return this.args.docs.slice(0, 4); + } + + /** + * Whether the toggle button should be shown. + * True if there are more than four docs. + */ + protected get toggleButtonIsShown() { + return this.args.docs.length > 4; + } + + /** + * The docs to show in the list. If the list is collapsed, + * we show the first four docs. Otherwise, we show all docs. + */ + protected get docsToShow() { + return this.isCollapsed ? this.firstFourDocs : this.args.docs; + } + + /** + * The action to take when the toggle button is clicked. + */ + @action protected toggleCollapsed() { + this.isCollapsed = !this.isCollapsed; + } +} + +declare module "@glint/environment-ember-loose/registry" { + export default interface Registry { + "Dashboard::DocsAwaitingReview": typeof DashboardDocsAwaitingReviewComponent; + } +} diff --git a/web/app/components/dashboard/docs-awaiting-review/doc.hbs b/web/app/components/dashboard/docs-awaiting-review/doc.hbs new file mode 100644 index 000000000..e83d5e4c6 --- /dev/null +++ b/web/app/components/dashboard/docs-awaiting-review/doc.hbs @@ -0,0 +1,70 @@ +
  • + +
    +
    + + {{! Avatar }} + + +
    + + {{! DocNumber & Title }} +
    + + + {{@doc.docNumber}} + + {{@doc.title}} + + + {{! Email }} +
    + + {{get @doc.owners 0}} + +
    +
    +
    + + {{! Product & DocType }} +
    + + +
    +
    +
    +
    +
  • diff --git a/web/app/components/dashboard/docs-awaiting-review/doc.ts b/web/app/components/dashboard/docs-awaiting-review/doc.ts new file mode 100644 index 000000000..9ee68b596 --- /dev/null +++ b/web/app/components/dashboard/docs-awaiting-review/doc.ts @@ -0,0 +1,20 @@ +import Component from "@glimmer/component"; +import { HermesDocument } from "hermes/types/document"; + +interface DashboardDocsAwaitingReviewDocComponentSignature { + Element: null; + Args: { + doc: HermesDocument; + }; + Blocks: { + default: []; + }; +} + +export default class DashboardDocsAwaitingReviewDocComponent extends Component {} + +declare module "@glint/environment-ember-loose/registry" { + export default interface Registry { + "Dashboard::DocsAwaitingReview::Doc": typeof DashboardDocsAwaitingReviewDocComponent; + } +} diff --git a/web/app/components/person/avatar.hbs b/web/app/components/person/avatar.hbs new file mode 100644 index 000000000..4a7ac8be0 --- /dev/null +++ b/web/app/components/person/avatar.hbs @@ -0,0 +1,21 @@ +
    + {{#if @imgURL}} + + {{else}} +
    + {{#if @email}} + + {{get-first-letter @email}} + + {{else}} + + {{/if}} +
    + {{/if}} +
    diff --git a/web/app/components/person/avatar.ts b/web/app/components/person/avatar.ts new file mode 100644 index 000000000..88fb3aa8f --- /dev/null +++ b/web/app/components/person/avatar.ts @@ -0,0 +1,34 @@ +import Component from "@glimmer/component"; + +enum HermesAvatarSize { + Small = "small", + Medium = "medium", +} + +interface PersonAvatarComponentSignature { + Element: HTMLDivElement; + Args: { + imgURL?: string | null; + email: string; + size: `${HermesAvatarSize}`; + }; + Blocks: { + default: []; + }; +} + +export default class PersonAvatarComponent extends Component { + protected get sizeIsSmall(): boolean { + return this.args.size === HermesAvatarSize.Small; + } + + protected get sizeIsMedium(): boolean { + return this.args.size === HermesAvatarSize.Medium; + } +} + +declare module "@glint/environment-ember-loose/registry" { + export default interface Registry { + "Person::Avatar": typeof PersonAvatarComponent; + } +} diff --git a/web/app/components/person/index.hbs b/web/app/components/person/index.hbs index 22f3f4ec4..bb6dc554d 100644 --- a/web/app/components/person/index.hbs +++ b/web/app/components/person/index.hbs @@ -13,25 +13,7 @@ /> {{/if}} -
    - {{#if @imgURL}} - - {{else}} -
    - {{#if @email}} - - {{get-first-letter @email}} - - {{else}} - - {{/if}} -
    - {{/if}} -
    +
    + {{yield}} diff --git a/web/app/components/truncated-text.ts b/web/app/components/truncated-text.ts index c91fb01ea..1222e7950 100644 --- a/web/app/components/truncated-text.ts +++ b/web/app/components/truncated-text.ts @@ -4,13 +4,21 @@ interface TruncatedTextComponentSignature { Element: HTMLSpanElement; Args: { tagName?: string; + startingBreakpoint?: "md"; }; Blocks: { default: []; }; } -export default class TruncatedTextComponent extends Component {} +export default class TruncatedTextComponent extends Component { + protected get class(): string { + if (this.args.startingBreakpoint === "md") { + return "starting-breakpoint-md"; + } + return "default"; + } +} declare module "@glint/environment-ember-loose/registry" { export default interface Registry { diff --git a/web/app/controllers/authenticated/dashboard.js b/web/app/controllers/authenticated/dashboard.js deleted file mode 100644 index b06eaa1cb..000000000 --- a/web/app/controllers/authenticated/dashboard.js +++ /dev/null @@ -1,15 +0,0 @@ -import Controller from "@ember/controller"; -import { alias } from "@ember/object/computed"; -import { inject as service } from "@ember/service"; - -export default class AuthenticatedDashboardController extends Controller { - @alias("model.docsWaitingForReview") docsWaitingForReview; - - @service router; - @service authenticatedUser; - @service("config") configSvc; - @service("recently-viewed-docs") recentDocs; - - queryParams = ["latestUpdates"]; - latestUpdates = "newDocs"; -} diff --git a/web/app/controllers/authenticated/dashboard.ts b/web/app/controllers/authenticated/dashboard.ts new file mode 100644 index 000000000..ad4ed4fca --- /dev/null +++ b/web/app/controllers/authenticated/dashboard.ts @@ -0,0 +1,10 @@ +import Controller from "@ember/controller"; +import { inject as service } from "@ember/service"; +import AuthenticatedUserService from "hermes/services/authenticated-user"; +import RecentlyViewedDocsService from "hermes/services/recently-viewed-docs"; + +export default class AuthenticatedDashboardController extends Controller { + @service declare authenticatedUser: AuthenticatedUserService; + @service("recently-viewed-docs") + declare recentDocs: RecentlyViewedDocsService; +} diff --git a/web/app/routes/authenticated/dashboard.js b/web/app/routes/authenticated/dashboard.ts similarity index 56% rename from web/app/routes/authenticated/dashboard.js rename to web/app/routes/authenticated/dashboard.ts index 31738b025..77b944963 100644 --- a/web/app/routes/authenticated/dashboard.js +++ b/web/app/routes/authenticated/dashboard.ts @@ -1,59 +1,46 @@ import Route from "@ember/routing/route"; -import RSVP from "rsvp"; import { inject as service } from "@ember/service"; +import AlgoliaService from "hermes/services/algolia"; +import ConfigService from "hermes/services/config"; +import FetchService from "hermes/services/fetch"; +import RecentlyViewedDocsService from "hermes/services/recently-viewed-docs"; +import SessionService from "hermes/services/session"; +import AuthenticatedUserService from "hermes/services/authenticated-user"; + +// @ts-ignore - Not yet typed import timeAgo from "hermes/utils/time-ago"; +import { HermesDocument } from "hermes/types/document"; export default class DashboardRoute extends Route { - @service algolia; - @service("config") configSvc; - @service("fetch") fetchSvc; - @service("recently-viewed-docs") recentDocs; - @service session; - @service authenticatedUser; - - queryParams = { - latestUpdates: { - refreshModel: true, - replace: true, - }, - }; - - resetController(controller, isExiting, transition) { - if (isExiting) { - controller.set("latestUpdates", "newDocs"); - } - } - - async model(params) { - // Create facet filter for recently updated docs depending on the selected - // "Latest updates" tab. - let facetFilter = ""; - if (params.latestUpdates == "approved") { - facetFilter = "status:approved"; - } else if (params.latestUpdates == "inReview") { - facetFilter = "status:In-Review"; - } + @service declare algolia: AlgoliaService; + @service("config") declare configSvc: ConfigService; + @service("fetch") declare fetchSvc: FetchService; + @service("recently-viewed-docs") + declare recentDocs: RecentlyViewedDocsService; + @service declare session: SessionService; + @service declare authenticatedUser: AuthenticatedUserService; + async model(): Promise { const userInfo = this.authenticatedUser.info; - const docsWaitingForReview = this.algolia.searchIndex + const docsAwaitingReview = await this.algolia.searchIndex .perform(this.configSvc.config.algolia_docs_index_name, "", { filters: `approvers:'${userInfo.email}'` + ` AND NOT approvedBy:'${userInfo.email}'` + " AND appCreated:true" + " AND status:In-Review", - hitsPerPage: 4, }) .then((result) => { // Add modifiedAgo for each doc. for (const hit of result.hits) { this.fetchSvc .fetch("/api/v1/documents/" + hit.objectID) - .then((resp) => resp.json()) + .then((resp) => resp?.json()) .then((doc) => { if (doc.modifiedTime) { const modifiedDate = new Date(doc.modifiedTime * 1000); + // @ts-ignore hit.modifiedAgo = `Modified ${timeAgo(modifiedDate)}`; } }) @@ -64,34 +51,31 @@ export default class DashboardRoute extends Route { ); }); } - return result.hits; + return result.hits as HermesDocument[]; }); - await this.recentDocs.fetchAll.perform(); - if (this.recentDocs.all === null) { - try { - await this.recentDocs.fetchAll.perform(); - } catch { - /** - * This tells our template to show the error state. - */ - this.recentDocs.all = null; - } + try { + await this.recentDocs.fetchAll.perform(); + } catch { + /** + * This tells our template to show the error state. + */ + this.recentDocs.all = null; } - return RSVP.hash({ - docsWaitingForReview: docsWaitingForReview, - }); + return docsAwaitingReview; } /** * Builds a parent query string for searching for Google files. The folders * parameter is an array of all folder ID strings to search. */ + // @ts-ignore - Not yet typed buildParentsQuery(folders) { let parentsQuery = ""; if (folders.length > 0) { parentsQuery += " and ("; + // @ts-ignore - Not yet typed folders.forEach((folder, index) => { if (index == 0) { parentsQuery += `'${folder}' in parents`; diff --git a/web/app/styles/app.scss b/web/app/styles/app.scss index a12f54a80..c23cd6759 100644 --- a/web/app/styles/app.scss +++ b/web/app/styles/app.scss @@ -2,10 +2,11 @@ @use "@hashicorp/design-system-components"; +@use "./variables"; + @use "./animations"; @use "./typography"; - @use "components/toolbar"; @use "components/tooltip"; @use "components/popover"; @@ -88,11 +89,11 @@ ol { } .x-container { - @apply w-full max-w-screen-lg mx-auto px-8; + @apply mx-auto w-full max-w-screen-lg px-8; } h1 { - @apply text-display-500 font-bold text-color-foreground-strong mb-1.5; + @apply mb-1.5 text-display-500 font-bold text-color-foreground-strong; + p { @apply text-body-300; diff --git a/web/app/styles/hermes/variables b/web/app/styles/hermes/variables new file mode 100644 index 000000000..e69de29bb diff --git a/web/app/styles/typography.scss b/web/app/styles/typography.scss index c688146a5..6a73424e2 100644 --- a/web/app/styles/typography.scss +++ b/web/app/styles/typography.scss @@ -1,5 +1,7 @@ +@use "variables" as v; + .hermes-form-label { - @apply text-body-200 font-semibold flex text-color-foreground-strong; + @apply flex text-body-200 font-semibold text-color-foreground-strong; + .hermes-form-helper-text { @apply mt-1; @@ -11,9 +13,22 @@ } .hermes-h4 { - @apply text-color-foreground-faint uppercase tracking-wide font-medium text-body-100; + @apply text-body-100 font-medium uppercase tracking-wide text-color-foreground-faint; } .truncated-text-container { - @apply truncate block font-regular text-body-100 text-color-foreground-faint; + @apply block text-body-100 font-regular text-color-foreground-faint; + + // These classes are applied by the TruncatedText component + // based on its optional `startingBreakpoint` argument: + + &.default { + @apply truncate; + } + + &.starting-breakpoint-md { + @media (min-width: v.$screen-md) { + @apply truncate; + } + } } diff --git a/web/app/styles/variables.scss b/web/app/styles/variables.scss new file mode 100644 index 000000000..3326f59eb --- /dev/null +++ b/web/app/styles/variables.scss @@ -0,0 +1,6 @@ +// Breakpoints +$screen-sm: 640px; +$screen-md: 768px; +$screen-lg: 1024px; +$screen-xl: 1280px; +$screen-2xl: 1536px; diff --git a/web/app/templates/authenticated/dashboard.hbs b/web/app/templates/authenticated/dashboard.hbs index 4a44423e9..60c71cf87 100644 --- a/web/app/templates/authenticated/dashboard.hbs +++ b/web/app/templates/authenticated/dashboard.hbs @@ -6,40 +6,18 @@ -
    -

    Welcome back, {{this.authenticatedUser.info.given_name}}

    -

    Here’s all the latest updates across the organization.

    -
    - - {{#if this.docsWaitingForReview}} -
    -
    - -

    Documents waiting for your review

    -
    +

    + Welcome back, + {{this.authenticatedUser.info.given_name}}! +

    -
    - {{#each this.docsWaitingForReview as |doc|}} - - {{/each}} -
    -
    + {{#if @model}} + {{/if}} -
    +

    {{#each this.recentDocs.all as |r|}} { + docMatches = schema.document.all().models.filter((doc) => { + return ( + doc.attrs.title.toLowerCase().includes(query.toLowerCase()) || + doc.attrs.product.toLowerCase().includes(query.toLowerCase()) + ); + }); + }; + const filters = requestBody.filters; if (filters?.includes("NOT objectID")) { @@ -88,13 +97,23 @@ export default function (mirageConfig) { // Duplicates are detected in the front end return new Response(200, {}, { hits: docMatches }); + } else if (filters) { + const requestIsForDocsAwaitingReview = + filters.includes("approvers:'testuser@example.com'") && + requestBody.filters.includes("AND status:In-Review"); + if (requestIsForDocsAwaitingReview) { + docMatches = schema.document.all().models.filter((doc) => { + return ( + doc.attrs.approvers.includes("testuser@example.com") && + doc.attrs.status.toLowerCase().includes("review") + ); + }); + } else { + // This + setDefaultDocMatches(); + } } else { - docMatches = schema.document.all().models.filter((doc) => { - return ( - doc.attrs.title.toLowerCase().includes(query.toLowerCase()) || - doc.attrs.product.toLowerCase().includes(query.toLowerCase()) - ); - }); + setDefaultDocMatches(); } if (idsToExclude) { diff --git a/web/mirage/factories/document.ts b/web/mirage/factories/document.ts index 486081b3f..7313271c9 100644 --- a/web/mirage/factories/document.ts +++ b/web/mirage/factories/document.ts @@ -40,5 +40,6 @@ export default Factory.extend({ value: "This is a test document", }, }, + approvers: [], owners: ["testuser@example.com"], }); diff --git a/web/tests/integration/components/dashboard/docs-awaiting-review-test.ts b/web/tests/integration/components/dashboard/docs-awaiting-review-test.ts new file mode 100644 index 000000000..c27646a48 --- /dev/null +++ b/web/tests/integration/components/dashboard/docs-awaiting-review-test.ts @@ -0,0 +1,104 @@ +import { module, test } from "qunit"; +import { setupRenderingTest } from "ember-qunit"; +import { MirageTestContext, setupMirage } from "ember-cli-mirage/test-support"; +import { click, render } from "@ember/test-helpers"; +import { hbs } from "ember-cli-htmlbars"; +import { HermesDocument } from "hermes/types/document"; + +const DOCS_AWAITING_REVIEW_COUNT_SELECTOR = + "[data-test-docs-awaiting-review-count]"; + +const DOC_AWAITING_REVIEW_LINK_SELECTOR = + "[data-test-doc-awaiting-review-link]"; + +const TOGGLE_SELECTOR = "[data-test-docs-awaiting-review-toggle]"; + +const TOGGLE_ICON = "[data-test-docs-awaiting-review-toggle-icon]"; + +interface DashboardDocsAwaitingReviewTestContext extends MirageTestContext { + docs: HermesDocument[]; +} + +module( + "Integration | Component | dashboard/docs-awaiting-review", + function (hooks) { + setupRenderingTest(hooks); + setupMirage(hooks); + + test("it shows different text depending on the number of docs awaiting review", async function (this: DashboardDocsAwaitingReviewTestContext, assert) { + this.server.create("document", { + title: "Foo", + status: "In Review", + approvers: ["testuser@example.com"], + }); + + this.server.create("document", { + title: "Bar", + status: "In Review", + approvers: ["testuser@example.com"], + }); + + this.set("docs", this.server.schema.document.all().models); + + await render( + hbs`` + ); + + assert.dom(DOCS_AWAITING_REVIEW_COUNT_SELECTOR).containsText("2"); + assert.dom(DOC_AWAITING_REVIEW_LINK_SELECTOR).exists({ count: 2 }); + + assert.dom("h2").containsText("documents awaiting your review"); + + this.set("docs", [this.server.schema.document.first()]); + + assert.dom(DOCS_AWAITING_REVIEW_COUNT_SELECTOR).containsText("1"); + assert.dom(DOC_AWAITING_REVIEW_LINK_SELECTOR).exists({ count: 1 }); + + assert.dom("h2").containsText("document awaiting your review"); + }); + + test("it shows a toggle button when there are more than 4 docs awaiting review", async function (this: DashboardDocsAwaitingReviewTestContext, assert) { + const docTitles = ["Foo", "Bar", "Baz", "Oof", "Zab"]; + + docTitles.forEach((title) => { + this.server.create("document", { + title, + status: "In Review", + approvers: ["testuser@example.com"], + }); + }); + + this.set("docs", this.server.schema.document.all().models); + + await render( + hbs`` + ); + + assert.dom(DOCS_AWAITING_REVIEW_COUNT_SELECTOR).containsText("5"); + assert.dom(DOC_AWAITING_REVIEW_LINK_SELECTOR).exists({ count: 4 }); + + assert.dom(TOGGLE_SELECTOR).hasText("Show all"); + assert.dom(TOGGLE_ICON).hasAttribute("data-test-icon", "plus"); + + await click(TOGGLE_SELECTOR); + + assert.dom(TOGGLE_SELECTOR).hasText("Show fewer"); + assert.dom(TOGGLE_ICON).hasAttribute("data-test-icon", "minus"); + + assert.dom(DOC_AWAITING_REVIEW_LINK_SELECTOR).exists({ count: 5 }); + + await click(TOGGLE_SELECTOR); + + assert.dom(TOGGLE_SELECTOR).hasText("Show all"); + assert.dom(TOGGLE_ICON).hasAttribute("data-test-icon", "plus"); + assert.dom(DOC_AWAITING_REVIEW_LINK_SELECTOR).exists({ count: 4 }); + + // Set the count to 1 to remove the need for the toggle + this.set("docs", [this.server.schema.document.first()]); + + assert + .dom(TOGGLE_SELECTOR) + .doesNotExist("toggle not shown when there's fewer than 4 docs"); + }); + } +); diff --git a/web/tests/integration/components/dashboard/docs-awaiting-review/doc-test.ts b/web/tests/integration/components/dashboard/docs-awaiting-review/doc-test.ts new file mode 100644 index 000000000..af3960e6f --- /dev/null +++ b/web/tests/integration/components/dashboard/docs-awaiting-review/doc-test.ts @@ -0,0 +1,90 @@ +import { module, test } from "qunit"; +import { setupRenderingTest } from "ember-qunit"; +import { MirageTestContext, setupMirage } from "ember-cli-mirage/test-support"; +import { find, render } from "@ember/test-helpers"; +import { hbs } from "ember-cli-htmlbars"; +import { HermesDocument } from "hermes/types/document"; + +const DOC_AWAITING_REVIEW_LINK_SELECTOR = + "[data-test-doc-awaiting-review-link]"; + +const DOC_AWAITING_REVIEW_NUMBER_AND_TITLE_SELECTOR = + "[data-test-doc-awaiting-review-number-and-title]"; + +const DOC_AWAITING_REVIEW_OWNER_SELECTOR = + "[data-test-doc-awaiting-review-owner]"; + +const DOC_AWAITING_REVIEW_PRODUCT_BADGE_SELECTOR = + "[data-test-doc-awaiting-review-product-badge]"; + +const DOC_AWAITING_REVIEW_DOCTYPE_BADGE_SELECTOR = + "[data-test-doc-awaiting-review-doctype-badge]"; + +interface DashboardDocsAwaitingReviewDocTestContext extends MirageTestContext { + doc: HermesDocument; +} + +module( + "Integration | Component | dashboard/docs-awaiting-review/doc", + function (hooks) { + setupRenderingTest(hooks); + setupMirage(hooks); + + test("it renders as expected", async function (this: DashboardDocsAwaitingReviewDocTestContext, assert) { + this.server.create("document", { + objectID: 10, + title: "Foo", + product: "Cloud Platform", + status: "In Review", + docType: "PRFAQ", + owners: ["foo@example.com"], + approvers: ["testuser@example.com"], + }); + + this.set("doc", this.server.schema.document.first()); + + await render(hbs` + + `); + + assert + .dom(DOC_AWAITING_REVIEW_LINK_SELECTOR) + .containsText("Foo") + .hasAttribute("href", "/document/10"); + + assert + .dom( + find( + `${DOC_AWAITING_REVIEW_LINK_SELECTOR} ${DOC_AWAITING_REVIEW_NUMBER_AND_TITLE_SELECTOR}` + ) + ) + .hasText("HCP-001 Foo", "Shows the doc number and title"); + + assert + .dom( + find( + `${DOC_AWAITING_REVIEW_LINK_SELECTOR} ${DOC_AWAITING_REVIEW_OWNER_SELECTOR}` + ) + ) + .hasText("foo@example.com", "Shows the doc owner"); + + assert + .dom( + find( + `${DOC_AWAITING_REVIEW_LINK_SELECTOR} ${DOC_AWAITING_REVIEW_PRODUCT_BADGE_SELECTOR}` + ) + ) + .hasText("Cloud Platform", "Shows the product name"); + + assert + .dom( + find( + `${DOC_AWAITING_REVIEW_LINK_SELECTOR} ${DOC_AWAITING_REVIEW_DOCTYPE_BADGE_SELECTOR}` + ) + ) + .hasText("PRFAQ", "Shows the doc type"); + }); + } +); diff --git a/web/tests/integration/components/truncated-text-test.ts b/web/tests/integration/components/truncated-text-test.ts index 9499e1ccd..b5241e046 100644 --- a/web/tests/integration/components/truncated-text-test.ts +++ b/web/tests/integration/components/truncated-text-test.ts @@ -3,11 +3,14 @@ import { setupRenderingTest } from "ember-qunit"; import { find, render } from "@ember/test-helpers"; import { hbs } from "ember-cli-htmlbars"; import { assert as emberAssert } from "@ember/debug"; +import window from "ember-window-mock"; +import { setupWindowMock } from "ember-window-mock/test-support"; const CONTAINER_SELECTOR = ".truncated-text-container"; module("Integration | Component | truncated-text", function (hooks) { setupRenderingTest(hooks); + setupWindowMock(hooks); test("it truncates text", async function (assert) { await render(hbs` @@ -18,8 +21,6 @@ module("Integration | Component | truncated-text", function (hooks) {

    `); - // TODO: Take Percy screenshot - //

    tag is used if no `tagName` is provided const container = find(`p${CONTAINER_SELECTOR}`); const text = find(`${CONTAINER_SELECTOR} > span`); @@ -43,6 +44,18 @@ module("Integration | Component | truncated-text", function (hooks) { assert.equal(textFontSize, "28px"); }); + test("it can truncate starting at a specific endpoint", async function (assert) { + await render(hbs` +

    + + This is a very long text that should be truncated + +
    + `); + + assert.dom(CONTAINER_SELECTOR).hasClass("starting-breakpoint-md"); + }); + test("it truncates text with a custom tag", async function (assert) { await render(hbs`
    diff --git a/web/types/glint/index.d.ts b/web/types/glint/index.d.ts index db52bde83..0dd6b0c17 100644 --- a/web/types/glint/index.d.ts +++ b/web/types/glint/index.d.ts @@ -8,6 +8,7 @@ import AndHelper from "ember-truth-helpers/helpers/and"; import EqHelper from "ember-truth-helpers/helpers/eq"; import IsEmptyHelper from "ember-truth-helpers/helpers/is-empty"; import LtHelper from "ember-truth-helpers/helpers/lt"; +import GtHelper from "ember-truth-helpers/helpers/gt"; import NotHelper from "ember-truth-helpers/helpers/not"; import OrHelper from "ember-truth-helpers/helpers/or"; import EmberSetBodyClassHelper from "ember-set-body-class"; @@ -60,6 +61,7 @@ declare module "@glint/environment-ember-loose/registry" { and: typeof AndHelper; not: typeof NotHelper; lt: typeof LtHelper; + gt: typeof GtHelper; "is-empty": IsEmptyHelper; FlashMessage: FlashMessageComponent; FlightIcon: FlightIconComponent;