Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e959114
feat(extension): welcome-back cover on hijacking new-tab
tsahimatsliah Jun 1, 2026
beaa879
feat(extension): richer welcome-back cover UI
tsahimatsliah Jun 1, 2026
3a026e9
feat(extension): brand-authentic welcome-back hero cover
tsahimatsliah Jun 1, 2026
41daaff
feat(extension): borderless, full-width aurora hero cover
tsahimatsliah Jun 1, 2026
a89ec1c
feat(extension): product-led hero with live feed peek
tsahimatsliah Jun 1, 2026
e8eb1eb
feat(extension): animate feed peek as a live, endless feed
tsahimatsliah Jun 1, 2026
292b382
feat(extension): full-bleed living feed wall hero
tsahimatsliah Jun 1, 2026
75779ea
feat(extension): sharpen hijacking hero voice
tsahimatsliah Jun 1, 2026
ec2858d
feat(extension): refine hijacking hero editorially
tsahimatsliah Jun 1, 2026
beb7df3
feat(extension): simplify hijacking hero with cat artwork
tsahimatsliah Jun 1, 2026
1417774
feat(extension): add polished stage background to hijacking hero
tsahimatsliah Jun 1, 2026
22c4f32
feat(extension): sharpen hijacking hero headline
tsahimatsliah Jun 1, 2026
53dda2c
feat(extension): align hijacking hero primary CTA with theme
tsahimatsliah Jun 1, 2026
1b875a1
fix(extension): wait for sign-back before hijacking impression
tsahimatsliah Jun 1, 2026
e15e63b
feat(extension): add onboarding-inspired hijacking hero variation
tsahimatsliah Jun 1, 2026
38b6d75
feat(extension): center hijacking onboarding auth hero
tsahimatsliah Jun 1, 2026
f539e1e
feat(extension): tighten hijacking onboarding hero
tsahimatsliah Jun 1, 2026
8b19095
Merge branch 'main' into feat/hijacking-strip-conversion
tsahimatsliah Jun 2, 2026
8a953d8
Merge branch 'main' into feat/hijacking-strip-conversion
tsahimatsliah Jun 2, 2026
14fc996
Merge branch 'main' into feat/hijacking-strip-conversion
tsahimatsliah Jun 2, 2026
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
223 changes: 205 additions & 18 deletions packages/extension/src/newtab/HijackingLoginStrip.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { AuthContextData } from '@dailydotdev/shared/src/contexts/AuthContext';
import { useAuthContext } from '@dailydotdev/shared/src/contexts/AuthContext';
import { getLogContextStatic } from '@dailydotdev/shared/src/contexts/LogContext';
import { useSignBack } from '@dailydotdev/shared/src/hooks/auth/useSignBack';
import {
AuthDisplay,
SocialProvider,
} from '@dailydotdev/shared/src/components/auth/common';
import { AuthTriggers } from '@dailydotdev/shared/src/lib/auth';
import { onboardingUrl } from '@dailydotdev/shared/src/lib/constants';
import { LogEvent, TargetType } from '@dailydotdev/shared/src/lib/log';
Expand All @@ -14,10 +20,54 @@ jest.mock('@dailydotdev/shared/src/contexts/AuthContext', () => ({
useAuthContext: jest.fn(),
}));

jest.mock('@dailydotdev/shared/src/hooks/auth/useSignBack', () => ({
useSignBack: jest.fn(),
}));

jest.mock('@dailydotdev/shared/src/components/auth/AuthOptions', () => {
const { AuthDisplay: MockAuthDisplay } = jest.requireActual(
'@dailydotdev/shared/src/components/auth/common',
);

return {
__esModule: true,
default: ({
onAuthStateUpdate,
}: {
onAuthStateUpdate?: (props: { defaultDisplay?: string }) => void;
}) => (
<div>
<button type="button">Continue with Google</button>
<button type="button">Continue with GitHub</button>
<button
type="button"
onClick={() =>
onAuthStateUpdate?.({
defaultDisplay: MockAuthDisplay.Registration,
})
}
>
Continue with email
</button>
<button
type="button"
onClick={() =>
onAuthStateUpdate?.({ defaultDisplay: MockAuthDisplay.Default })
}
>
Log in
</button>
<p>By continuing, you agree to the Terms of Service</p>
</div>
),
};
});

const LogContext = getLogContextStatic();
const mockUseAuthContext = useAuthContext as jest.MockedFunction<
typeof useAuthContext
>;
const mockUseSignBack = useSignBack as jest.MockedFunction<typeof useSignBack>;
const logEvent = jest.fn();
const showLogin = jest.fn();

Expand Down Expand Up @@ -61,40 +111,155 @@ const renderComponent = (
...authContext,
});

return render(
<LogContext.Provider
value={{
logEvent,
logEventStart: jest.fn(),
logEventEnd: jest.fn(),
sendBeacon: jest.fn(),
}}
>
<HijackingLoginStrip />
</LogContext.Provider>,
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});

const Wrapper = ({
children,
}: {
children: React.ReactNode;
}): React.ReactElement => (
<QueryClientProvider client={queryClient}>
<LogContext.Provider
value={{
logEvent,
logEventStart: jest.fn(),
logEventEnd: jest.fn(),
sendBeacon: jest.fn(),
}}
>
{children}
</LogContext.Provider>
</QueryClientProvider>
);

return render(<HijackingLoginStrip />, { wrapper: Wrapper });
};

beforeEach(() => {
jest.clearAllMocks();
mockUseSignBack.mockReturnValue({
isLoaded: true,
signBack: undefined,
provider: undefined,
onUpdateSignBack: jest.fn(),
});
});

describe('HijackingLoginStrip', () => {
it('shows a login CTA for logged out users', () => {
it('encourages signup for logged out users without a remembered account', () => {
renderComponent();

expect(
screen.getByRole('heading', {
name: 'Where developers make every tab count.',
}),
).toBeVisible();

expect(
screen.getByRole('button', { name: 'Continue with Google' }),
).toBeVisible();
expect(
screen.getByRole('button', { name: 'Continue with GitHub' }),
).toBeVisible();
expect(
screen.getByRole('button', { name: 'Continue with email' }),
).toBeVisible();
expect(
screen.getByText(/By continuing, you agree to the Terms of Service/),
).toBeVisible();

fireEvent.click(
screen.getByRole('button', { name: 'Continue with email' }),
);

expect(showLogin).toHaveBeenCalledWith({
trigger: AuthTriggers.Onboarding,
options: {
isLogin: false,
defaultDisplay: AuthDisplay.Registration,
formValues: undefined,
},
});
});

it('logs a signup impression for new visitors', () => {
renderComponent();

expect(logEvent).toHaveBeenCalledWith({
event_name: LogEvent.Impression,
target_type: TargetType.SignupButton,
target_id: 'hijacking',
});
});

it('waits for remembered account storage before logging the impression', () => {
let signBackState: ReturnType<typeof useSignBack> = {
isLoaded: false,
signBack: undefined,
provider: undefined,
onUpdateSignBack: jest.fn(),
};

mockUseSignBack.mockImplementation(() => signBackState);

const { rerender } = renderComponent();

expect(logEvent).not.toHaveBeenCalled();

signBackState = {
isLoaded: true,
signBack: {
name: 'Tsahi Matsliah',
email: 'tsahi@daily.dev',
image: 'https://daily.dev/tsahi.png',
},
provider: SocialProvider.Google,
onUpdateSignBack: jest.fn(),
};

rerender(<HijackingLoginStrip />);

expect(
screen.getByText('Unlock the full daily.dev experience'),
screen.getByRole('heading', { name: /Welcome back, Tsahi/ }),
).toBeVisible();
expect(logEvent).toHaveBeenCalledWith({
event_name: LogEvent.Impression,
target_type: TargetType.LoginButton,
target_id: 'hijacking',
});
expect(logEvent).not.toHaveBeenCalledWith({
event_name: LogEvent.Impression,
target_type: TargetType.SignupButton,
target_id: 'hijacking',
});
});

it('offers a welcome-back "Continue as" for users with a remembered account', () => {
mockUseSignBack.mockReturnValue({
isLoaded: true,
signBack: {
name: 'Tsahi Matsliah',
email: 'tsahi@daily.dev',
image: 'https://daily.dev/tsahi.png',
},
provider: SocialProvider.Google,
onUpdateSignBack: jest.fn(),
});

renderComponent();

expect(
screen.getByText('Log in to pick up where you left off.'),
screen.getByRole('heading', { name: /Welcome back, Tsahi/ }),
).toBeVisible();
expect(screen.getByText('tsahi@daily.dev')).toBeVisible();

const cta = screen.getByRole('button', { name: 'Log in to continue' });
const cta = screen.getByRole('button', { name: /Continue as Tsahi/ });
fireEvent.click(cta);

expect(logEvent).toHaveBeenCalledWith({
event_name: LogEvent.Click,
event_name: LogEvent.Impression,
target_type: TargetType.LoginButton,
target_id: 'hijacking',
});
Expand All @@ -104,16 +269,38 @@ describe('HijackingLoginStrip', () => {
});
});

it('lets remembered users create a different account', () => {
mockUseSignBack.mockReturnValue({
isLoaded: true,
signBack: {
name: 'Tsahi Matsliah',
email: 'tsahi@daily.dev',
image: 'https://daily.dev/tsahi.png',
},
provider: SocialProvider.Google,
onUpdateSignBack: jest.fn(),
});

renderComponent();

fireEvent.click(screen.getByRole('button', { name: 'Create an account' }));

expect(showLogin).toHaveBeenCalledWith({
trigger: AuthTriggers.Onboarding,
options: { isLogin: false },
});
});

it('shows an onboarding CTA for logged in users who still need onboarding', () => {
renderComponent({ user: loggedUser, isLoggedIn: true });

expect(
screen.getByText(
'You still have a few onboarding steps left. Finish them to unlock the full experience.',
'Finish onboarding to unlock the full daily.dev experience.',
),
).toBeVisible();

const cta = screen.getByRole('link', { name: 'Continue onboarding' });
const cta = screen.getByRole('link', { name: /Continue/ });
const expectedUrl = new URL(onboardingUrl);
expectedUrl.searchParams.append('r', 'extension');

Expand Down
Loading
Loading