diff --git a/packages/content-suggestions/dist/angular/angular/content-text-with-suggestions.component.js b/packages/content-suggestions/dist/angular/angular/content-text-with-suggestions.component.js
index f89735b..72e9ce8 100644
--- a/packages/content-suggestions/dist/angular/angular/content-text-with-suggestions.component.js
+++ b/packages/content-suggestions/dist/angular/angular/content-text-with-suggestions.component.js
@@ -29,7 +29,7 @@ export class ContentTextWithSuggestionsComponent {
[renderLink]="renderLink"
(viewed)="viewed.emit()"
(expandedChange)="expandedChange.emit($event)"
- />
+ >
`, isInline: true, dependencies: [{ kind: "component", type: ContentTextComponent, selector: "content-text", inputs: ["className", "weight", "text", "ellipsis", "blur", "style", "onView", "renderMention", "renderTag", "renderLink"], outputs: ["viewed", "expandedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ContentTextWithSuggestionsComponent, decorators: [{
@@ -53,7 +53,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
[renderLink]="renderLink"
(viewed)="viewed.emit()"
(expandedChange)="expandedChange.emit($event)"
- />
+ >
`,
}]
}], propDecorators: { className: [{
diff --git a/packages/content-suggestions/dist/angular/angular/content-title-with-suggestions.component.js b/packages/content-suggestions/dist/angular/angular/content-title-with-suggestions.component.js
index 45ebca8..e93828a 100644
--- a/packages/content-suggestions/dist/angular/angular/content-title-with-suggestions.component.js
+++ b/packages/content-suggestions/dist/angular/angular/content-title-with-suggestions.component.js
@@ -34,7 +34,7 @@ export class ContentTitleWithSuggestionsComponent {
[renderLink]="renderLink"
(viewed)="viewed.emit()"
(expandedChange)="expandedChange.emit($event)"
- />
+ >
`, isInline: true, dependencies: [{ kind: "component", type: ContentTextWithSuggestionsComponent, selector: "content-text-with-suggestions", inputs: ["className", "weight", "text", "ellipsis", "blur", "style", "onView", "renderMention", "renderTag", "renderLink"], outputs: ["viewed", "expandedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ContentTitleWithSuggestionsComponent, decorators: [{
@@ -58,7 +58,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
[renderLink]="renderLink"
(viewed)="viewed.emit()"
(expandedChange)="expandedChange.emit($event)"
- />
+ >
`,
}]
}], propDecorators: { className: [{
diff --git a/packages/content-suggestions/package.json b/packages/content-suggestions/package.json
index f6a2c20..fb44da7 100644
--- a/packages/content-suggestions/package.json
+++ b/packages/content-suggestions/package.json
@@ -1,6 +1,6 @@
{
"name": "@hublib-web/content-suggestions",
- "version": "0.2.0",
+ "version": "0.2.1",
"description": "Content text/title with mentions, tags and links for React and Angular",
"license": "MIT",
"type": "module",
@@ -51,7 +51,7 @@
"build:angular": "ngc -p tsconfig.angular.json && node ./scripts/fix-angular-entry.mjs",
"clean": "rm -rf dist storybook-static",
"typecheck": "tsc -p tsconfig.json --noEmit",
- "test": "vitest run --passWithNoTests",
+ "test": "yarn run -T vitest run --passWithNoTests",
"lint": "eslint src --ext .ts,.tsx",
"storybook": "storybook dev -p 6006",
"storybook:build": "storybook build"
@@ -59,7 +59,7 @@
"peerDependencies": {
"@angular/common": ">=17.0.0",
"@angular/core": ">=17.0.0",
- "@hublib-web/tach-typography": "0.3.0",
+ "@hublib-web/tach-typography": "0.3.1",
"antd": ">=5.0.0",
"react": ">=18.0.0",
"react-dom": ">=18.0.0",
@@ -90,7 +90,7 @@
"@angular/compiler": "^20.3.17",
"@angular/compiler-cli": "^20.3.17",
"@angular/core": "^20.3.17",
- "@hublib-web/tach-typography": "workspace:0.3.0",
+ "@hublib-web/tach-typography": "workspace:0.3.1",
"@storybook/addon-essentials": "8.6.14",
"@storybook/react": "8.6.14",
"@storybook/react-vite": "8.6.14",
diff --git a/packages/content-suggestions/src/angular/content-text-with-suggestions.component.ts b/packages/content-suggestions/src/angular/content-text-with-suggestions.component.ts
index 5b0464f..b085c19 100644
--- a/packages/content-suggestions/src/angular/content-text-with-suggestions.component.ts
+++ b/packages/content-suggestions/src/angular/content-text-with-suggestions.component.ts
@@ -30,7 +30,7 @@ import { ContentTextComponent } from "./content-text.component";
[renderLink]="renderLink"
(viewed)="viewed.emit()"
(expandedChange)="expandedChange.emit($event)"
- />
+ >
`,
})
export class ContentTextWithSuggestionsComponent {
diff --git a/packages/content-suggestions/src/angular/content-title-with-suggestions.component.ts b/packages/content-suggestions/src/angular/content-title-with-suggestions.component.ts
index 4bf3670..41891a8 100644
--- a/packages/content-suggestions/src/angular/content-title-with-suggestions.component.ts
+++ b/packages/content-suggestions/src/angular/content-title-with-suggestions.component.ts
@@ -29,7 +29,7 @@ import { ContentTextWithSuggestionsComponent } from "./content-text-with-suggest
[renderLink]="renderLink"
(viewed)="viewed.emit()"
(expandedChange)="expandedChange.emit($event)"
- />
+ >
`,
})
export class ContentTitleWithSuggestionsComponent {
diff --git a/packages/content-suggestions/src/angular/index.test.ts b/packages/content-suggestions/src/angular/index.test.ts
new file mode 100644
index 0000000..29c3fcc
--- /dev/null
+++ b/packages/content-suggestions/src/angular/index.test.ts
@@ -0,0 +1,97 @@
+// @vitest-environment jsdom
+
+import "@angular/compiler";
+
+import { Component, provideZonelessChangeDetection } from "@angular/core";
+import { TestBed, getTestBed } from "@angular/core/testing";
+import { BrowserTestingModule, platformBrowserTesting } from "@angular/platform-browser/testing";
+import { afterEach, describe, expect, it } from "vitest";
+
+import {
+ ContentTextWithSuggestionsComponent,
+ ContentTitleWithSuggestionsComponent,
+} from "../../dist/angular/index.js";
+
+const ensureAngularTestEnvironment = (): void => {
+ try {
+ getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting());
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error);
+ if (!message.includes("Cannot set base providers")) {
+ throw error;
+ }
+ }
+};
+
+ensureAngularTestEnvironment();
+
+@Component({
+ standalone: true,
+ imports: [ContentTextWithSuggestionsComponent, ContentTitleWithSuggestionsComponent],
+ template: `
+
+
+ `,
+})
+class ContentSuggestionsHostComponent {
+ text =
+ "Привет @[John Doe](d290f1ee-6c54-4b01-90e6-d701748f0851) #frontend смотри example.com";
+ title = "Заголовок @[Jane Roe](123e4567-e89b-12d3-a456-426614174000) #news docs.example.com";
+}
+
+describe("content-suggestions (angular)", () => {
+ afterEach(() => {
+ TestBed.resetTestingModule();
+ });
+
+ it("renders text with mention, tag and link tokens", async () => {
+ TestBed.configureTestingModule({
+ providers: [provideZonelessChangeDetection()],
+ });
+
+ const fixture = TestBed.createComponent(ContentSuggestionsHostComponent);
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const host: HTMLElement = fixture.nativeElement;
+ const content = host.querySelector("content-text-with-suggestions");
+ expect(content).not.toBeNull();
+
+ const text = content?.textContent ?? "";
+ expect(text).toContain("Привет");
+ expect(text).toContain("@John Doe");
+ expect(text).toContain("#frontend");
+ expect(text).toContain("example.com");
+
+ const link = content?.querySelector('a.tach-typography[href="https://example.com"]');
+ expect(link).not.toBeNull();
+ expect(link?.textContent).toContain("example.com");
+ });
+
+ it("renders title variant and keeps entities visible", async () => {
+ TestBed.configureTestingModule({
+ providers: [provideZonelessChangeDetection()],
+ });
+
+ const fixture = TestBed.createComponent(ContentSuggestionsHostComponent);
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const host: HTMLElement = fixture.nativeElement;
+ const title = host.querySelector("content-title-with-suggestions");
+ expect(title).not.toBeNull();
+
+ const text = title?.textContent ?? "";
+ expect(text).toContain("Заголовок");
+ expect(text).toContain("@Jane Roe");
+ expect(text).toContain("#news");
+ expect(text).toContain("docs.example.com");
+
+ const boldNode = title?.querySelector(".tach-typography--bold");
+ expect(boldNode).not.toBeNull();
+
+ const link = title?.querySelector('a.tach-typography[href="https://docs.example.com"]');
+ expect(link).not.toBeNull();
+ expect(link?.textContent).toContain("docs.example.com");
+ });
+});
diff --git a/packages/content-suggestions/src/stories/ContentSuggestions.AngularDOM.stories.tsx b/packages/content-suggestions/src/stories/ContentSuggestions.AngularDOM.stories.tsx
new file mode 100644
index 0000000..0e67e4a
--- /dev/null
+++ b/packages/content-suggestions/src/stories/ContentSuggestions.AngularDOM.stories.tsx
@@ -0,0 +1,177 @@
+import "@angular/compiler";
+
+import React, { useEffect, useRef, useState } from "react";
+
+import { createComponent, provideZonelessChangeDetection, type ApplicationRef } from "@angular/core";
+import { createApplication } from "@angular/platform-browser";
+import type { Meta, StoryObj } from "@storybook/react";
+import { expect, waitFor, within } from "@storybook/test";
+
+import {
+ ContentTextWithSuggestionsComponent,
+ ContentTitleWithSuggestionsComponent,
+} from "../../dist/angular/index.js";
+
+const meta = {
+ title: "Angular/ContentSuggestions DOM",
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component:
+ "Angular runtime verification in Storybook: text and title components with mention/tag/link rendered into real DOM.",
+ },
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+type MountedTextComponent = ReturnType>;
+type MountedTitleComponent = ReturnType>;
+
+const TEXT_SAMPLE =
+ "Angular text: @[Иван](123e4567-e89b-12d3-a456-426614174000) #frontend docs.example.com";
+const TITLE_SAMPLE =
+ "Angular title: @[Мария](123e4567-e89b-12d3-a456-426614174001) #release github.com";
+
+const createSection = (title: string): HTMLDivElement => {
+ const section = document.createElement("div");
+ section.style.display = "grid";
+ section.style.gap = "8px";
+ section.style.padding = "10px 12px";
+ section.style.border = "1px solid #e2e8f0";
+ section.style.borderRadius = "10px";
+
+ const heading = document.createElement("div");
+ heading.style.fontFamily =
+ 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Courier New", monospace';
+ heading.style.fontSize = "12px";
+ heading.style.color = "#475569";
+ heading.textContent = title;
+
+ const body = document.createElement("div");
+ section.append(heading, body);
+ body.dataset.slot = "body";
+
+ return section;
+};
+
+const AngularContentDomHarness: React.FC = () => {
+ const hostRef = useRef(null);
+ const [status, setStatus] = useState("mounting");
+
+ useEffect(() => {
+ const host = hostRef.current;
+ if (!host) {
+ return;
+ }
+
+ let cancelled = false;
+ let appRef: ApplicationRef | null = null;
+ let textRef: MountedTextComponent | null = null;
+ let titleRef: MountedTitleComponent | null = null;
+
+ host.innerHTML = "";
+ setStatus("mounting");
+
+ void createApplication({
+ providers: [provideZonelessChangeDetection()],
+ })
+ .then(app => {
+ if (cancelled) {
+ app.destroy();
+ return;
+ }
+
+ appRef = app;
+
+ const textSection = createSection("ContentTextWithSuggestionsComponent");
+ const titleSection = createSection("ContentTitleWithSuggestionsComponent");
+ textSection.dataset.testid = "angular-content-text";
+ titleSection.dataset.testid = "angular-content-title";
+ host.append(textSection, titleSection);
+
+ const textHost = textSection.querySelector('[data-slot="body"]') as HTMLElement;
+ const titleHost = titleSection.querySelector('[data-slot="body"]') as HTMLElement;
+
+ textRef = createComponent(ContentTextWithSuggestionsComponent, {
+ environmentInjector: app.injector,
+ hostElement: textHost,
+ });
+ app.attachView(textRef.hostView);
+ textRef.setInput("text", TEXT_SAMPLE);
+ textRef.setInput("ellipsis", false);
+ textRef.changeDetectorRef.detectChanges();
+
+ titleRef = createComponent(ContentTitleWithSuggestionsComponent, {
+ environmentInjector: app.injector,
+ hostElement: titleHost,
+ });
+ app.attachView(titleRef.hostView);
+ titleRef.setInput("text", TITLE_SAMPLE);
+ titleRef.setInput("ellipsis", false);
+ titleRef.changeDetectorRef.detectChanges();
+
+ setStatus("ready");
+ })
+ .catch(error => {
+ setStatus(`error: ${String(error)}`);
+ });
+
+ return () => {
+ cancelled = true;
+ textRef?.destroy();
+ titleRef?.destroy();
+ appRef?.destroy();
+ };
+ }, []);
+
+ return (
+
+
+ status={status}
+
+
+
+ );
+};
+
+export const TextAndTitleRenderCheck: Story = {
+ render: () => ,
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+
+ await waitFor(() => {
+ expect(canvas.getByText("status=ready")).toBeTruthy();
+ });
+
+ await waitFor(() => {
+ expect(canvas.getByText("Angular text:")).toBeTruthy();
+ expect(canvas.getByText("@Иван")).toBeTruthy();
+ expect(canvas.getByText("#frontend")).toBeTruthy();
+ expect(canvas.getByText("docs.example.com")).toBeTruthy();
+ expect(canvas.getByText("Angular title:")).toBeTruthy();
+ expect(canvas.getByText("@Мария")).toBeTruthy();
+ expect(canvas.getByText("#release")).toBeTruthy();
+ expect(canvas.getByText("github.com")).toBeTruthy();
+ });
+
+ const docsLink = canvas.getByRole("link", { name: "docs.example.com" });
+ expect(docsLink.getAttribute("href")).toBe("https://docs.example.com");
+
+ const githubLink = canvas.getByRole("link", { name: "github.com" });
+ expect(githubLink.getAttribute("href")).toBe("https://github.com");
+ },
+};