Skip to content

Ui5 ai notice indicator task #11158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
196 changes: 196 additions & 0 deletions packages/ai/cypress/specs/AINoticeIndicator.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import AINoticeIndicator from "../../src/AINoticeIndicator.js";
import {
AI_NOTICE_INDICATOR_CLOSE_BUTTON_TEXT,
AI_NOTICE_INDICATOR_POPOVER_CONTENT,
AI_NOTICE_INDICATOR_ATTRIBUTIONTEXT,
AI_NOTICE_INDICATOR_VERIFICATIONTEXT,
} from "../../src/generated/i18n/i18n-defaults.js";

describe("AI Notice Indicator", () => {
describe("Rendering and Interaction", () => {
beforeEach(() => {
cy.mount(<AINoticeIndicator></AINoticeIndicator>);
});

it('Should render the component', () => {
cy.get('ui5-ai-notice-indicator').should('exist');
});

it("Should display correct elements for Default mode", () => {

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-link]")
.should('not.have.attr', 'icon');

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-label]")
.should("exist");
});

it("Should display correct elements for Emphasized mode", () => {
cy.get('ui5-ai-notice-indicator').invoke('attr', 'mode', 'Emphasized');

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-link]")
.should('have.attr', 'icon', 'ai')
.should("exist");

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-label]")
.should("exist");
});

it("Should display correct elements for Shortened mode", () => {
cy.get('ui5-ai-notice-indicator').invoke('attr', 'mode', 'Shortened');

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-link]")
.should('not.have.attr', 'icon');

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-link]")
.should("exist");
});

it('Should display only the AI icon in IconOnly mode', () => {
cy.get('ui5-ai-notice-indicator').invoke('attr', 'mode', 'IconOnly');

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-button]")
.should('have.attr', 'icon', 'sap-icon://ai')
.should("exist");

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-link]")
.should("not.exist");
});
});

describe("Values of the texts", () => {
it("Should display correct values of the properties for Default mode", () => {
cy.mount(<AINoticeIndicator></AINoticeIndicator>);

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-link]")
.contains(AI_NOTICE_INDICATOR_ATTRIBUTIONTEXT.defaultText);

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-text]")
.contains(AI_NOTICE_INDICATOR_VERIFICATIONTEXT.defaultText);
});

it("Should display correct user-set values of the properties", () => {
cy.mount(<AINoticeIndicator attribution-text="Made with ai" verification-text="Check the results"></AINoticeIndicator>);

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-link]")
.contains("Made with ai");

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-label]")
.contains("Check the results");
});

it("Should display correct default values of the popover properties", () => {
cy.mount(<AINoticeIndicator></AINoticeIndicator>);

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-responsive-popover]")
.contains(AI_NOTICE_INDICATOR_CLOSE_BUTTON_TEXT.defaultText);

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-responsive-popover]")
.contains(AI_NOTICE_INDICATOR_POPOVER_CONTENT.defaultText);
});

it("Should display correct values of the popover properties", () => {
cy.mount(<AINoticeIndicator close-button-text="ok" popover-text="The Al-generated content may contain inaccuracies due to using multiple information sources. Verify results before use."></AINoticeIndicator>);

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-responsive-popover]")
.contains("ok");

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-responsive-popover]")
.contains("The Al-generated content may contain inaccuracies due to using multiple information sources. Verify results before use.");
});

});

describe("Popover open and close events", () => {
beforeEach(() => {
cy.mount(<AINoticeIndicator></AINoticeIndicator>);
});

it('Should open and close the popover when clicking on the link', () => {
cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-link]")
.realClick();

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-responsive-popover]")
.should("exist")
.should("have.prop", "open", true);

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-link]")
.realClick();

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-responsive-popover]")
.should("have.prop", "open", false);
});

it('Should close the popover when clicking on the close button', () => {
cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-link]")
.realClick();

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-button]")
.realClick();

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-responsive-popover]")
.should("exist")
.should("have.prop", "open", false);
});

it('Should open the popover from the icon button', () => {
cy.get('ui5-ai-notice-indicator').invoke('attr', 'mode', 'IconOnly');

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("#created-by-ai-button-link")
.realClick();

cy.get("[ui5-ai-notice-indicator]")
.shadow()
.find("[ui5-responsive-popover]")
.should("have.prop", "open", true);
});
});
});
158 changes: 158 additions & 0 deletions packages/ai/src/AINoticeIndicator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
import jsxRender from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
import type ResponsivePopover from "@ui5/webcomponents/dist/ResponsivePopover.js";
import AINoticeIndicatorMode from "./types/AINoticeIndicatorMode.js";
import {
AI_NOTICE_INDICATOR_CLOSE_BUTTON_TEXT,
AI_NOTICE_INDICATOR_POPOVER_CONTENT,
AI_NOTICE_INDICATOR_ATTRIBUTIONTEXT,
AI_NOTICE_INDICATOR_VERIFICATIONTEXT,
} from "./generated/i18n/i18n-defaults.js";

// Template
import AINoticeIndicatorTemplate from "./AINoticeIndicatorTemplate.js";

// Styles
import AINoticeIndicatorCss from "./generated/themes/AINoticeIndicator.css.js";

/**
* @class
*
* ### Overview
*
* The `ui5-ai-notice-indicator` component provides an AI-related notice that can include attribution and verification text.
* Depending on the mode chosen, the user can configure which parts of the component will be visible.
*
* ### Usage
*
* The component supports different display modes:
* - **Default**: Displays both attribution and verification text.
* - **Shortened**: Displays a condensed version of the notice without verification text.
* - **Emphasized**: Includes an icon and both attribution and verification text.
* - **IconOnly**: Only displays an icon. Note: This mode is not recommended, we suggest always providing a text.
*
* The `ui5-ai-notice-indicator` opens a popover on interaction, and includes a close button to dismiss it.
*
* ### ES6 Module Import
*
* `import "@ui5/webcomponents/dist/AINoticeIndicator.js";`
*
* @constructor
* @extends UI5Element
* @public
* @since 2.6.0
*/

@customElement({
tag: "ui5-ai-notice-indicator",
renderer: jsxRender,
styles: AINoticeIndicatorCss,
template: AINoticeIndicatorTemplate,
})

class AINoticeIndicator extends UI5Element {
/**
* Determines the AI attribution notice text.
*
* @default undefined
* @public
*/
@property()
attributionText?: string;

/**
* Determines the verification prompt text.
*
* @default undefined
* @public
*/
@property()
verificationText?: string;

/**
* Determines content of the popover.
*
* @default undefined
* @public
*/
@property()
popoverText?: string;

/**
* Determines text of the close button inside the popover.
*
* @default undefined
* @public
*/
@property()
closeButtonText?: string;

/**
* Determines whether the mode of AI icon.
*
* @default "Default"
* @public
*/
@property()
mode: `${AINoticeIndicatorMode}` = "Default"

@property({ type: Boolean })
_popoverOpen = false;

@i18n("@ui5/webcomponents")
static i18nBundle: I18nBundle;

get isIconOnly() {
return this.mode === AINoticeIndicatorMode.IconOnly;
}

get isShortened() {
return this.mode === AINoticeIndicatorMode.Shortened;
}

get isEmphasized() {
return this.mode === AINoticeIndicatorMode.Emphasized;
}

get _attributionText() {
return this.attributionText || AINoticeIndicator.i18nBundle.getText(AI_NOTICE_INDICATOR_ATTRIBUTIONTEXT);
}

get _verificationText() {
return this.verificationText || AINoticeIndicator.i18nBundle.getText(AI_NOTICE_INDICATOR_VERIFICATIONTEXT);
}

get _closeButtonText() {
return this.closeButtonText || AINoticeIndicator.i18nBundle.getText(AI_NOTICE_INDICATOR_CLOSE_BUTTON_TEXT);
}

get _popoverText() {
return this.popoverText || AINoticeIndicator.i18nBundle.getText(AI_NOTICE_INDICATOR_POPOVER_CONTENT);
}

get _popover(): ResponsivePopover | null {
return this.shadowRoot?.querySelector("ui5-responsive-popover") as ResponsivePopover;
}

_handleToggleClick() {
const popover = this._popover;
if (popover) {
popover.open = !popover.open;
}
}

_handlePopoverClose() {
const popover = this._popover;
if (popover) {
popover.open = false;
}
}
}

AINoticeIndicator.define();

export default AINoticeIndicator;
Loading
Loading