Skip to content

Commit 3eee7ec

Browse files
authored
Use xAPI Forwarding (#131)
* drop in new xapi code and tests alongside old * feed course url to share btn * instrument share button with shared learning resource * remove xapi instrumentation from view button * remove xapi instrumentation from course spotlight card * remove xapi instrumentation from searchresult card * instrument create saved search modal with saved search statement * added new instrumentation to save list modal * added new instrumentation to saved modal course page * added explored instrumentation to course page * expanded test for course page * fix index test * added search instrumentation to index page * remove instrumentation from list page * fix one test in search * migrated search to new instrumentation * rip out old instrumentation * fix header test * fixed default layout test * fix final failing test why not * refactor xAPi mock * mock specific xapi events and migrate tests * further refactor * test expectation of saved * add negative case too * update test for index search
1 parent 361b208 commit 3eee7ec

36 files changed

+1132
-706
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"@headlessui/react": "^1.4.2",
77
"@heroicons/react": "^1.0.5",
88
"@tailwindcss/line-clamp": "^0.3.0",
9-
"@xapi/xapi": "^2.0.1",
109
"autoprefixer": "^10.4.0",
1110
"axios": "^0.23.0",
1211
"next": "12.1.0",

src/__mocks__/mockXapi.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as Xapi from '@/utils/xapi';
2+
import * as XapiEvents from '@/utils/xapi/events';
3+
4+
function keepFreshAndMock(nsObj, mockedFnName, mockImpl) {
5+
if (nsObj[mockedFnName].mockRestore) {
6+
nsObj[mockedFnName].mockRestore();
7+
}
8+
jest.spyOn(nsObj, mockedFnName).mockImplementation(mockImpl);
9+
}
10+
11+
export function mockSendStatement() {
12+
keepFreshAndMock(Xapi, 'sendStatement', () => Promise.resolve({}));
13+
}
14+
15+
export function mockXapiEvents() {
16+
['searched', 'saved', 'curated', 'shared', 'explored', 'viewed'].forEach(
17+
(fnName) => keepFreshAndMock(XapiEvents, fnName, jest.fn())
18+
);
19+
}

src/__mocks__/predefinedMocks.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,3 +1097,13 @@ export function useMockUserListWithDifferentUserId() {
10971097
isError: false,
10981098
}));
10991099
}
1100+
1101+
export function useMockClipboard() {
1102+
Object.defineProperty(navigator, 'clipboard', {
1103+
value: {
1104+
writeText: jest.fn().mockResolvedValue(undefined),
1105+
writeText: jest.fn().mockResolvedValue('foo'),
1106+
},
1107+
writable: true,
1108+
});
1109+
}

src/__tests__/components/Header.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { render } from '@testing-library/react';
22
import { useAuth } from '@/contexts/AuthContext';
3+
import { useMockConfig } from '@/__mocks__/predefinedMocks';
34
import Header from '@/components/Header';
45
import mockRouter from 'next-router-mock';
56

@@ -10,6 +11,7 @@ jest.mock('../../contexts/AuthContext', () => ({
1011
}));
1112
beforeEach(() => {
1213
mockRouter.setCurrentUrl('/');
14+
useMockConfig();
1315
});
1416

1517
// This is all you need:
Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
import { act, fireEvent, render } from '@testing-library/react';
2-
import { useUnauthenticatedUser } from '@/__mocks__/predefinedMocks';
2+
import { mockXapiEvents } from '@/__mocks__/mockXapi';
3+
import { shared } from '@/utils/xapi/events';
4+
import {
5+
useMockClipboard,
6+
useUnauthenticatedUser,
7+
} from '@/__mocks__/predefinedMocks';
38
import ShareButton from '@/components/buttons/ShareBtn';
4-
import mock, { xAPISendStatement } from '@/utils/xapi/xAPISendStatement';
59

6-
const mockXAPISendStatement = jest.fn();
710
describe('ShareButton', () => {
8-
jest.mock('@/utils/xapi/xAPISendStatement');
9-
10-
it('should render correctly', () => {
11+
beforeEach(() => {
12+
mockXapiEvents();
1113
useUnauthenticatedUser();
14+
useMockClipboard();
15+
});
16+
it('should render correctly', () => {
1217
const screen = render(<ShareButton />);
1318

1419
expect(screen.getByText('Share')).toBeEnabled();
1520
});
16-
it.skip('should call xAPISendStatement', async () => {
17-
mock.xAPISendStatement.mockImplementation(mockXAPISendStatement);
18-
useUnauthenticatedUser();
21+
it('should call sendStatement', async () => {
1922
const screen = render(<ShareButton />);
2023
await act(async () => {
2124
fireEvent.click(screen.getByText('Share'));
2225
});
23-
expect(mockXAPISendStatement).toHaveBeenCalled();
26+
expect(shared).toHaveBeenCalled();
27+
expect(navigator.clipboard.writeText).toHaveBeenCalled();
2428
});
2529
});
Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider';
22
import { act, fireEvent, render, screen } from '@testing-library/react';
33
import { useAuth } from '@/contexts/AuthContext';
4-
import ViewBtn from "@/components/buttons/ViewBtn";
4+
import ViewBtn from '@/components/buttons/ViewBtn';
55
import courseData from '@/__mocks__/data/course.data';
6-
import xAPIMapper from "@/utils/xapi/xAPIMapper";
76

87
// mock auth
98
jest.mock('@/contexts/AuthContext', () => ({
@@ -19,7 +18,11 @@ const renderer = (data = courseData) => {
1918

2019
return render(
2120
<MemoryRouterProvider url='/'>
22-
<ViewBtn id={data.meta.id} courseTitle={data.courseTitle} courseDescription={data.courseDescription} />
21+
<ViewBtn
22+
id={data.meta.id}
23+
courseTitle={data.courseTitle}
24+
courseDescription={data.courseDescription}
25+
/>
2326
</MemoryRouterProvider>
2427
);
2528
};
@@ -30,16 +33,4 @@ describe('ShareBtn', () => {
3033

3134
expect(getByRole('button').id).not.toBeNull();
3235
});
33-
34-
it('send xAPI statement when course is clicked', () => {
35-
const { getByRole } = renderer();
36-
37-
const spy = jest.spyOn(xAPIMapper, 'sendStatement')
38-
.mockImplementation(() => Promise.resolve({})
39-
);
40-
41-
fireEvent.click(getByRole('button'));
42-
43-
expect(spy).toHaveBeenCalled();
44-
})
4536
});

src/__tests__/components/cards/CourseSpotlight.test.js

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import CourseSpotlight from '@/components/cards/CourseSpotlight';
66
import courseData from '@/__mocks__/data/course.data';
77
import mockRouter from 'next-router-mock';
88
import uiConfigData from '@/__mocks__/data/uiConfig.data';
9-
import xAPIMapper from "@/utils/xapi/xAPIMapper";
109

1110
// jest mocks
1211
jest.mock('next/dist/client/router', () => require('next-router-mock'));
@@ -58,7 +57,7 @@ describe('Course Spotlight', () => {
5857
Course_Instance: { Thumbnail: 'fake.img' },
5958
};
6059
const { getByAltText, queryByRole } = renderer(modified);
61-
expect(getByAltText ('')).toBeInTheDocument();
60+
expect(getByAltText('')).toBeInTheDocument();
6261

6362
// expect(queryByRole('img')).toBeInTheDocument();
6463
});
@@ -71,20 +70,6 @@ describe('Course Spotlight', () => {
7170
});
7271
});
7372
});
74-
75-
it('send xAPI statement when course is clicked', () => {
76-
const { getByText } = renderer();
77-
78-
const spy = jest.spyOn(xAPIMapper, 'sendStatement')
79-
.mockImplementation(() => Promise.resolve({})
80-
);
81-
82-
act(() => {
83-
fireEvent.click(getByText(/Test Course Title/i).parentElement);
84-
});
85-
86-
expect(spy).toHaveBeenCalled();
87-
})
8873
});
8974

9075
it('renders', () => {

src/__tests__/components/modals/CreateSavedSearch.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { QueryClientWrapper } from '@/__mocks__/queryClientMock';
22
import { act } from 'react-dom/test-utils';
3+
import { mockXapiEvents } from '@/__mocks__/mockXapi';
4+
import { saved } from '@/utils/xapi/events';
35
import {
46
createSaveSearchMockFn,
57
useAuthenticatedUser,
@@ -18,6 +20,10 @@ const renderer = () => {
1820
);
1921
};
2022

23+
beforeEach(() => {
24+
mockXapiEvents();
25+
});
26+
2127
afterEach(() => {
2228
jest.resetAllMocks();
2329
});
@@ -57,6 +63,7 @@ describe('CreateSavedSearchModal', () => {
5763
fireEvent.click(getByText('Save'));
5864
});
5965
expect(createSaveSearchMockFn).toHaveBeenCalled();
66+
expect(saved).toHaveBeenCalled();
6067
});
6168

6269
it('should not call the api when there is no query to save', () => {
@@ -70,6 +77,7 @@ describe('CreateSavedSearchModal', () => {
7077
fireEvent.click(getByText('Save'));
7178
});
7279
expect(createSaveSearchMockFn).not.toHaveBeenCalled();
80+
expect(saved).not.toHaveBeenCalled();
7381
});
7482
});
7583

src/__tests__/components/modals/SaveModal.test.js

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { act, fireEvent, render, screen } from '@testing-library/react';
2+
import { curated } from '@/utils/xapi/events';
3+
import { mockXapiEvents } from '@/__mocks__/mockXapi';
24
import SaveModal from '@/components/modals/SaveModal';
35

46
import { QueryClientWrapper } from '@/__mocks__/queryClientMock';
57
import { useAuth } from '@/contexts/AuthContext';
68
import { useCreateUserList } from '@/hooks/useCreateUserList';
79
import { useUpdateUserList } from '@/hooks/useUpdateUserList';
810
import { useUserOwnedLists } from '@/hooks/useUserOwnedLists.js';
9-
import { xAPISendStatement } from '@/utils/xapi/xAPISendStatement';
1011
import userListData from '@/__mocks__/data/userLists.data';
11-
import xAPIMapper from '@/utils/xapi/xAPIMapper';
1212

1313
jest.mock('@/hooks/useUpdateUserList', () => ({
1414
useUpdateUserList: jest.fn(),
@@ -60,7 +60,7 @@ const renderer = (isAuth = false) => {
6060
return render(
6161
<QueryClientWrapper>
6262
<div>
63-
<SaveModal courseId={'12345'} title={"test"} modalState={true} />
63+
<SaveModal courseId={'12345'} title={'test'} modalState={true} />
6464
</div>
6565
</QueryClientWrapper>
6666
);
@@ -80,6 +80,7 @@ beforeEach(() => {
8080
data: userListData,
8181
isSuccess: true,
8282
}));
83+
mockXapiEvents();
8384
});
8485

8586
describe('Save Modal', () => {
@@ -147,11 +148,8 @@ describe('Save Modal', () => {
147148
it.todo('should');
148149

149150
it.skip('should send xAPI statement when create is clicked', () => {
150-
const { getByText, getByPlaceholderText } = renderer(true);
151+
const { getByText, getAllByText, getByPlaceholderText } = renderer(true);
151152

152-
const spy = jest
153-
.spyOn(xAPISendStatement, 'xAPISendStatement')
154-
.mockImplementation(() => Promise.resolve({}));
155153
act(() => {
156154
fireEvent.click(getByText(/save/i));
157155
});
@@ -161,14 +159,12 @@ describe('Save Modal', () => {
161159
});
162160

163161
fireEvent.change(getByPlaceholderText(/List Description.../i), {
164-
target: { value: 'Descprition' },
162+
target: { value: 'Description' },
165163
});
166164

167-
act(() => {
168-
fireEvent.click(getByText(/create/i));
169-
});
165+
fireEvent.click(getByText(/create/i, { selector: 'input' }));
170166

171-
expect(spy).toHaveBeenCalled();
167+
expect(curated).toHaveBeenCalled();
172168
});
173169
});
174170
});

src/__tests__/layouts/DefaultLayout.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe('Default Layout', () => {
3535
renderer();
3636
expect(screen.getByText('Sign in')).toBeInTheDocument();
3737
expect(screen.getByText('Sign up')).toBeInTheDocument();
38-
expect(screen.getByAltText('home')).toBeInTheDocument();
38+
expect(screen.getByText('Home')).toBeInTheDocument();
3939

4040
expect(screen.getByText('DOD Home Page')).toBeInTheDocument();
4141
expect(screen.getByText('About ADL')).toBeInTheDocument();

0 commit comments

Comments
 (0)