Skip to content
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

refactor(experience): show dark favicon #6210

Merged
merged 1 commit into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions packages/experience/src/Providers/AppBoundary/AppMeta.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Theme } from '@logto/schemas';
import { conditionalString } from '@silverhand/essentials';
import classNames from 'classnames';
import i18next from 'i18next';
Expand All @@ -7,9 +8,15 @@ import { Helmet } from 'react-helmet';
import PageContext from '@/Providers/PageContextProvider/PageContext';
import defaultAppleTouchLogo from '@/assets/apple-touch-icon.png';
import defaultFavicon from '@/assets/favicon.png';
import { type SignInExperienceResponse } from '@/types';

import * as styles from './index.module.scss';

const themeToFavicon = Object.freeze({
[Theme.Light]: 'favicon',
[Theme.Dark]: 'darkFavicon',
} as const satisfies Record<Theme, keyof SignInExperienceResponse['branding']>);

/**
* User React Helmet to manage html and body attributes
* @see https://github.com/nfl/react-helmet
Expand All @@ -26,16 +33,13 @@ import * as styles from './index.module.scss';

const AppMeta = () => {
const { experienceSettings, theme, platform, isPreview } = useContext(PageContext);
const favicon = experienceSettings?.branding[themeToFavicon[theme]];

return (
<Helmet>
<html lang={i18next.language} data-theme={theme} />
<link rel="shortcut icon" href={experienceSettings?.branding.favicon ?? defaultFavicon} />
<link
rel="apple-touch-icon"
href={experienceSettings?.branding.favicon ?? defaultAppleTouchLogo}
sizes="180x180"
/>
<link rel="shortcut icon" href={favicon ?? defaultFavicon} />
<link rel="apple-touch-icon" href={favicon ?? defaultAppleTouchLogo} sizes="180x180" />
{experienceSettings?.customCss && <style>{experienceSettings.customCss}</style>}
<body
className={classNames(
Expand Down
78 changes: 62 additions & 16 deletions packages/integration-tests/src/tests/experience/overrides.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ import ExpectExperience from '#src/ui-helpers/expect-experience.js';

describe('override', () => {
const organizationApi = new OrganizationApiTest();
const logoUrl = 'mock://fake-url-for-omni/logo.png';
const darkLogoUrl = 'mock://fake-url-for-omni/dark-logo.png';
const primaryColor = '#000';
const darkPrimaryColor = '#fff';
const favicon = 'mock://fake-url-for-omni/favicon.ico';
const darkFavicon = 'mock://fake-url-for-omni/dark-favicon.ico';

afterEach(async () => {
await organizationApi.cleanUp();
Expand All @@ -25,7 +31,8 @@ describe('override', () => {
await updateSignInExperience({
termsOfUseUrl: null,
privacyPolicyUrl: null,
color: { primaryColor: '#000', darkPrimaryColor: '#fff', isDarkModeEnabled: true },
color: { primaryColor, darkPrimaryColor, isDarkModeEnabled: true },
branding: { logoUrl, darkLogoUrl, favicon, darkFavicon },
signUp: { identifiers: [], password: true, verify: false },
signIn: {
methods: [
Expand All @@ -40,7 +47,31 @@ describe('override', () => {
});
});

it('should show the overridden organization logos', async () => {
it('should show dark mode branding elements when dark mode is enabled', async () => {
const experience = new ExpectExperience(await browser.newPage());
await experience.page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]);
await experience.navigateTo(demoAppUrl.href);
await experience.toMatchElement('body[class$="dark"]');
await experience.toMatchElement(`img[src="${darkLogoUrl}"]`);

const button = await experience.toMatchElement('button[name="submit"]');
expect(
await button.evaluate((element) => window.getComputedStyle(element).backgroundColor)
).toBe('rgb(255, 255, 255)');

const foundFavicon = await experience.page.evaluate(() => {
return document.querySelector('link[rel="shortcut icon"]')?.getAttribute('href');
});
expect(foundFavicon).toBe(darkFavicon);

const faviconAppleTouch = await experience.page.evaluate(() => {
return document.querySelector('link[rel="apple-touch-icon"]')?.getAttribute('href');
});
expect(faviconAppleTouch).toBe(darkFavicon);
await experience.page.close();
});

it('should show the overridden organization logos and favicons', async () => {
const logoUrl = 'mock://fake-url-for-organization/logo.png';
const darkLogoUrl = 'mock://fake-url-for-organization/dark-logo.png';

Expand All @@ -60,13 +91,17 @@ describe('override', () => {
await experience.page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]);
await experience.navigateTo(demoAppUrl.href + `?organization_id=${organization.id}`);
await experience.toMatchElement(`img[src="${darkLogoUrl}"]`);

await experience.page.close();
});

it('should show app-level logo and color', async () => {
it('should show app-level logo, favicon, and color', async () => {
const logoUrl = 'mock://fake-url-for-app/logo.png';
const darkLogoUrl = 'mock://fake-url-for-app/dark-logo.png';
const primaryColor = '#f00';
const darkPrimaryColor = '#0f0';
const favicon = 'mock://fake-url-for-organization/favicon.ico';
const darkFavicon = 'mock://fake-url-for-organization/dark-favicon.ico';

const application = await createApplication(
'Sign-in experience override',
Expand All @@ -80,31 +115,41 @@ describe('override', () => {
);

await setApplicationSignInExperience(application.id, {
color: {
primaryColor,
darkPrimaryColor,
},
branding: {
logoUrl,
darkLogoUrl,
},
color: { primaryColor, darkPrimaryColor },
branding: { logoUrl, darkLogoUrl, favicon, darkFavicon },
});

const experience = new ExpectExperience(await browser.newPage());
const expectMatchBranding = async (theme: string, logoUrl: string, primaryColor: string) => {
const expectMatchBranding = async (
theme: string,
logoUrl: string,
primaryColor: string,
favicon: string
) => {
await experience.page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: theme }]);
await experience.navigateTo(demoAppUrl.href + `?app_id=${application.id}`);
await experience.toMatchElement(`img[src="${logoUrl}"]`);
const button1 = await experience.toMatchElement('button[name="submit"]');
const button = await experience.toMatchElement('button[name="submit"]');
expect(
await button1.evaluate((element) => window.getComputedStyle(element).backgroundColor)
await button.evaluate((element) => window.getComputedStyle(element).backgroundColor)
).toBe(primaryColor);

const foundFavicon = await experience.page.evaluate(() => {
return document.querySelector('link[rel="shortcut icon"]')?.getAttribute('href');
});
expect(foundFavicon).toBe(favicon);

const faviconAppleTouch = await experience.page.evaluate(() => {
return document.querySelector('link[rel="apple-touch-icon"]')?.getAttribute('href');
});
expect(faviconAppleTouch).toBe(favicon);
};

await expectMatchBranding('light', logoUrl, 'rgb(255, 0, 0)');
await expectMatchBranding('dark', darkLogoUrl, 'rgb(0, 255, 0)');
await expectMatchBranding('light', logoUrl, 'rgb(255, 0, 0)', favicon);
await expectMatchBranding('dark', darkLogoUrl, 'rgb(0, 255, 0)', darkFavicon);

await deleteApplication(application.id);
await experience.page.close();
});

it('should combine app-level and organization-level branding', async () => {
Expand Down Expand Up @@ -161,5 +206,6 @@ describe('override', () => {

await expectMatchBranding('light', organizationLogoUrl, 'rgb(0, 0, 255)');
await expectMatchBranding('dark', organizationDarkLogoUrl, 'rgb(255, 0, 255)');
await experience.page.close();
});
});
Loading