([]);
+ const nextItem = React.useCallback(() => setQueue(queue.slice(1)), [queue]);
+ const QueuedItem = queue.length > 0 ? queue[0] : null;
+
+ useEnqueueWhenReady(useStickyMicrosurveyContent, queue, setQueue);
+ useEnqueueWhenReady(useAdoptionMicrosurveyContent, queue, setQueue);
+
+ return [QueuedItem, nextItem];
+}
diff --git a/src/app/layouts/default/microsurvey-popup/sticky-content.js b/src/app/layouts/default/microsurvey-popup/sticky-content.js
deleted file mode 100644
index 9d64bd9fe..000000000
--- a/src/app/layouts/default/microsurvey-popup/sticky-content.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import React from 'react';
-import RawHTML from '~/components/jsx-helpers/raw-html';
-import {useStickyData, useSeenCounter} from '../shared';
-
-const SEEN_ENOUGH = 3;
-
-function StickyContent({stickyData, children}) {
- return (
-
- );
-}
-
-function useBoundStickyContent(stickyData, incrementSeenCount) {
- // Increment seen count on each fresh load
- React.useEffect(
- () => incrementSeenCount(),
- [incrementSeenCount]
- );
-
- return React.useCallback(
- (props) => ,
- [stickyData]
- );
-}
-
-export default function useStickyMicrosurveyContent() {
- const stickyData = useStickyData();
- const [hasBeenSeenEnough, incrementSeenCount] = useSeenCounter(SEEN_ENOUGH);
- const BoundStickyContent = useBoundStickyContent(stickyData, incrementSeenCount);
-
- const ready = Boolean(
- stickyData?.mode === 'popup' && !hasBeenSeenEnough
- );
-
- return [ready, BoundStickyContent];
-}
diff --git a/src/app/layouts/default/microsurvey-popup/sticky-content.tsx b/src/app/layouts/default/microsurvey-popup/sticky-content.tsx
new file mode 100644
index 000000000..5b80e5502
--- /dev/null
+++ b/src/app/layouts/default/microsurvey-popup/sticky-content.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import RawHTML from '~/components/jsx-helpers/raw-html';
+import {useStickyData, useSeenCounter, BannerInfo as StickyData} from '../shared';
+import {assertDefined} from '~/helpers/data';
+import type {QueuedItemType} from './queue';
+
+const SEEN_ENOUGH = 3;
+
+function StickyContent({stickyData, children}: {stickyData: StickyData; children: React.ReactNode}) {
+ const {body, link_url: url, link_text: text} = stickyData;
+
+ return (
+
+ );
+}
+
+function useBoundStickyContent(stickyData: StickyData | undefined, incrementSeenCount: () => void) {
+ // Increment seen count on each fresh load
+ React.useEffect(
+ () => incrementSeenCount(),
+ [incrementSeenCount]
+ );
+
+ // ready ensures it will not be called unless stickyData is defined
+ return React.useCallback(
+ (props: {children: React.ReactNode}) => ,
+ [stickyData]
+ );
+}
+
+export default function useStickyMicrosurveyContent(): [boolean, QueuedItemType] {
+ const stickyData = useStickyData();
+ const [hasBeenSeenEnough, incrementSeenCount] = useSeenCounter(SEEN_ENOUGH);
+ const BoundStickyContent = useBoundStickyContent(stickyData?.bannerInfo, incrementSeenCount);
+ const ready = Boolean(
+ stickyData?.mode === 'popup' && !hasBeenSeenEnough
+ );
+
+ return [ready, BoundStickyContent];
+}
diff --git a/src/app/layouts/default/shared.tsx b/src/app/layouts/default/shared.tsx
index afc982be0..25bc6cd73 100644
--- a/src/app/layouts/default/shared.tsx
+++ b/src/app/layouts/default/shared.tsx
@@ -4,7 +4,7 @@ import {useDataFromPromise} from '~/helpers/page-data-utils';
import PutAway from '~/components/put-away/put-away';
import './shared.scss';
-type BannerInfo = {
+export type BannerInfo = {
id: number;
heading: string;
body: string;
diff --git a/src/app/models/accounts-model.ts b/src/app/models/accounts-model.ts
index 20d36dfc1..68e863a7f 100644
--- a/src/app/models/accounts-model.ts
+++ b/src/app/models/accounts-model.ts
@@ -25,13 +25,13 @@ export type AccountsUserModel = {
uuid: string;
first_name: string;
last_name: string;
- email: string;
+ email?: string;
school_name: string;
self_reported_role: string;
self_reported_school: string;
is_not_gdpr_location: boolean;
salesforce_contact_id: string;
- is_instructor_verification_stale: boolean;
+ is_instructor_verification_stale?: boolean;
faculty_status: string;
contact_infos: {
type: string;
@@ -39,6 +39,7 @@ export type AccountsUserModel = {
is_verified: boolean;
is_guessed_preferred: boolean;
}[];
+ using_openstax: boolean;
};
declare global {
diff --git a/src/app/models/usermodel.ts b/src/app/models/usermodel.ts
index 743ec3ae5..c94ca9be1 100644
--- a/src/app/models/usermodel.ts
+++ b/src/app/models/usermodel.ts
@@ -13,7 +13,7 @@ export type UserModelType = {
last_name: string;
instructorEligible: boolean;
pending_verification: boolean;
- stale_verification: boolean;
+ stale_verification?: boolean;
incompleteSignup: boolean;
pendingInstructorAccess: boolean;
emailUnverified: boolean;
diff --git a/src/app/pages/k12/k12-main/banner.tsx b/src/app/pages/k12/k12-main/banner.tsx
index 1deca7b03..a5b35fcb3 100644
--- a/src/app/pages/k12/k12-main/banner.tsx
+++ b/src/app/pages/k12/k12-main/banner.tsx
@@ -52,7 +52,7 @@ export default function Banner({data}: {data: K12Data}) {
);
diff --git a/test/src/components/shell/microsurvey-popup/adoption-content.test.tsx b/test/src/components/shell/microsurvey-popup/adoption-content.test.tsx
new file mode 100644
index 000000000..504ea4cff
--- /dev/null
+++ b/test/src/components/shell/microsurvey-popup/adoption-content.test.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import {render, screen} from '@testing-library/preact';
+import userEvent from '@testing-library/user-event';
+import MemoryRouter from '~/../../test/helpers/future-memory-router';
+import useAdoptionMicrosurveyContent from '~/layouts/default/microsurvey-popup/adoption-content';
+import * as UM from '~/models/usermodel';
+import userModelData from '~/../../test/src/data/userModel';
+import { UserContextProvider } from '~/contexts/user';
+
+/* eslint-disable camelcase */
+function userModelAdopter(isAdopter: boolean) {
+ const adopterFields = {
+ using_openstax: isAdopter,
+ faculty_status: isAdopter ? 'confirmed_faculty' : 'no_faculty_info'
+ };
+
+ return {...userModelData, accountsModel: {...userModelData.accountsModel, ...adopterFields}};
+}
+/* eslint-enable camelcase */
+
+describe('microsurvey-popup/adoption-content', () => {
+ const user = userEvent.setup();
+ const saveError = console.error;
+
+ function Component() {
+ const [ready, AdoptionContent] = useAdoptionMicrosurveyContent();
+
+ return ready
+ ? the page
+ : null;
+ }
+
+ function WrappedComponent({path}: {path: string}) {
+ return
+
+
+
+ ;
+ }
+ it('renders nothing if user is not an adopter', async () => {
+ jest.spyOn(UM, 'useUserModel').mockReturnValue({...userModelAdopter(false)});
+ render();
+ expect(document.body.textContent).toBe('');
+ });
+ it('renders nothing on the renewal-form even if user is an adopter', () => {
+ jest.spyOn(UM, 'useUserModel').mockReturnValue({...userModelAdopter(true)});
+ render();
+ expect(document.body.textContent).toBe('');
+ });
+ it('renders the page on other paths if user is faculty/adopter', async () => {
+ jest.spyOn(UM, 'useUserModel').mockReturnValue({...userModelAdopter(true)});
+ render();
+ screen.getByText('the page');
+ const link = screen.getByRole('link');
+
+ console.error = jest.fn();
+ await user.click(link);
+ expect(console.error).toHaveBeenCalled(); // because navigation
+ console.error = saveError;
+ });
+});
diff --git a/test/src/components/shell/microsurvey-popup.test.tsx b/test/src/components/shell/microsurvey-popup/microsurvey-popup.test.tsx
similarity index 89%
rename from test/src/components/shell/microsurvey-popup.test.tsx
rename to test/src/components/shell/microsurvey-popup/microsurvey-popup.test.tsx
index 325b39112..2f83f0ee2 100644
--- a/test/src/components/shell/microsurvey-popup.test.tsx
+++ b/test/src/components/shell/microsurvey-popup/microsurvey-popup.test.tsx
@@ -1,7 +1,6 @@
import React from 'react';
-import {describe, expect, it} from '@jest/globals';
import {render} from '@testing-library/preact';
-import ShellContextProvider from '../../../helpers/shell-context';
+import ShellContextProvider from '../../../../helpers/shell-context';
import MemoryRouter from '~/../../test/helpers/future-memory-router';
import MicroSurvey from '~/layouts/default/microsurvey-popup/microsurvey-popup';
import useMSQueue from '~/layouts/default/microsurvey-popup/queue';
diff --git a/test/src/components/shell/microsurvey-popup/queue.test.tsx b/test/src/components/shell/microsurvey-popup/queue.test.tsx
new file mode 100644
index 000000000..a4b0d0209
--- /dev/null
+++ b/test/src/components/shell/microsurvey-popup/queue.test.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import {render, screen} from '@testing-library/preact';
+import useMSQueue from '~/layouts/default/microsurvey-popup/queue';
+import MemoryRouter from '~/../../test/helpers/future-memory-router';
+import * as S from '~/layouts/default/shared';
+import * as AC from '~/layouts/default/microsurvey-popup/adoption-content';
+import stickyData from '~/../../test/src/data/sticky-data';
+
+let itemsHandled = 0;
+
+function Component() {
+ const [QueuedItem, nextItem] = useMSQueue();
+
+ React.useEffect(() => {
+ if (QueuedItem) {
+ ++itemsHandled;
+ nextItem();
+ }
+ });
+
+ if (!QueuedItem) {
+ return {itemsHandled} item handled
;
+ }
+
+ return ;
+}
+
+function MockAdoptionContent() {
+ return Adoption stuff
;
+}
+
+describe('microsurvey queue', () => {
+ stickyData.mode = 'popup';
+ jest.spyOn(S, 'useStickyData').mockReturnValue(stickyData);
+ jest.spyOn(AC, 'default').mockReturnValue([true, MockAdoptionContent]);
+
+ it('enqueues sticky and adoption content', async () => {
+ render(
+
+ );
+ screen.getByText('1 item handled');
+ });
+});
diff --git a/test/src/components/shell/microsurvey-popup/sticky-content.test.tsx b/test/src/components/shell/microsurvey-popup/sticky-content.test.tsx
new file mode 100644
index 000000000..98c4a2d75
--- /dev/null
+++ b/test/src/components/shell/microsurvey-popup/sticky-content.test.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import {render, screen} from '@testing-library/preact';
+import MemoryRouter from '~/../../test/helpers/future-memory-router';
+import useStickyMicrosurveyContent from '~/layouts/default/microsurvey-popup/sticky-content';
+import * as SH from '~/layouts/default/shared';
+import stickyData from '~/../../test/src/data/sticky-data';
+
+describe('microsurvey-popup/sticky-content', () => {
+ function Component() {
+ const [ready, StickyContent] = useStickyMicrosurveyContent();
+
+ return ready
+ ? the page
+ : null;
+ }
+
+ function WrappedComponent({path}: {path: string}) {
+ return
+
+ ;
+ }
+
+ it('renders nothing unless stickydata mode is popup', () => {
+ jest.spyOn(SH, 'useStickyData').mockReturnValue({...stickyData, mode: 'banner'});
+ render();
+ expect(document.body.textContent).toBe('');
+ });
+ it('renders the page if stickydata mode is popup', () => {
+ jest.spyOn(SH, 'useStickyData').mockReturnValue({...stickyData, mode: 'popup'});
+ render();
+ screen.getByText('Make a difference now');
+ });
+});
diff --git a/test/src/data/sticky-data.ts b/test/src/data/sticky-data.ts
new file mode 100644
index 000000000..f9ba85427
--- /dev/null
+++ b/test/src/data/sticky-data.ts
@@ -0,0 +1,25 @@
+/* eslint-disable camelcase */
+export default {
+ mode: 'banner' as 'banner' | 'popup',
+ start: '2024-05-10T15:00:00Z',
+ expires: '2025-10-01T19:58:00Z',
+ show_popup: false,
+ header: 'Normal sticky',
+ body: 'By giving $1, $5, or $10 you can make a meaningful impact...',
+ link_text: 'Give now',
+ link: 'https://google.com',
+ emergency_expires: '2023-01-17T02:00:00Z',
+ emergency_content:
+ 'The OpenStax offices will be closed January 16 in observance of Martin Luther King, Jr. Day.',
+ bannerInfo: {
+ html_message:
+ 'Help students around the world succeed with contributions of $5, $10 or $20',
+ link_text: 'Make a difference now',
+ link_url: 'https://dev.openstax.org/give',
+ banner_thumbnail:
+ 'https://assets.openstax.org/oscms-dev/media/original_images/subj-icon-science.png',
+ id: 0,
+ heading: '',
+ body: ''
+ }
+};
diff --git a/test/src/layouts/lower-sticky-note.test.tsx b/test/src/layouts/lower-sticky-note.test.tsx
index 8f9d99565..d0c5066f1 100644
--- a/test/src/layouts/lower-sticky-note.test.tsx
+++ b/test/src/layouts/lower-sticky-note.test.tsx
@@ -3,34 +3,9 @@ import {render, screen} from '@testing-library/preact';
import * as S from '~/layouts/default/shared';
import LowerStickyNote from '~/layouts/default/lower-sticky-note/lower-sticky-note';
import userEvent from '@testing-library/user-event';
+import stickyData from '~/../../test/src/data/sticky-data';
import Cookies from 'js-cookie';
-/* eslint-disable camelcase */
-const stickyData = {
- mode: 'banner' as const,
- start: '2024-05-10T15:00:00Z',
- expires: '2025-10-01T19:58:00Z',
- show_popup: false,
- header: 'Normal sticky',
- body: 'By giving $1, $5, or $10 you can make a meaningful impact...',
- link_text: 'Give now',
- link: 'https://google.com',
- emergency_expires: '2023-01-17T02:00:00Z',
- emergency_content:
- 'The OpenStax offices will be closed January 16 in observance of Martin Luther King, Jr. Day.',
- bannerInfo: {
- html_message:
- 'Help students around the world succeed with contributions of $5, $10 or $20',
- link_text: 'Make a difference now',
- link_url: 'https://dev.openstax.org/give',
- banner_thumbnail:
- 'https://assets.openstax.org/oscms-dev/media/original_images/subj-icon-science.png',
- id: 0,
- heading: '',
- body: ''
- }
-};
-
/* eslint-disable camelcase */
describe('lower-sticky-note', () => {
const user = userEvent.setup();