Add Onboarding controls to Redux

revert-5af0e40a
Justin 2022-04-12 09:51:28 -04:00 zatwierdzone przez Alex Gleason
rodzic 3bd8ef13ef
commit c8c715ee4b
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 7211D1F99744FBB7
7 zmienionych plików z 210 dodań i 8 usunięć

Wyświetl plik

@ -0,0 +1,101 @@
import { mockStore, mockWindowProperty } from 'soapbox/jest/test-helpers';
import rootReducer from 'soapbox/reducers';
import { checkOnboardingStatus, startOnboarding, endOnboarding } from '../onboarding';
describe('checkOnboarding()', () => {
let mockGetItem: any;
mockWindowProperty('localStorage', {
getItem: (key: string) => mockGetItem(key),
});
beforeEach(() => {
mockGetItem = jest.fn().mockReturnValue(null);
});
it('does nothing if localStorage item is not set', async() => {
mockGetItem = jest.fn().mockReturnValue(null);
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
const store = mockStore(state);
await store.dispatch(checkOnboardingStatus());
const actions = store.getActions();
expect(actions).toEqual([]);
expect(mockGetItem.mock.calls.length).toBe(1);
});
it('does nothing if localStorage item is invalid', async() => {
mockGetItem = jest.fn().mockReturnValue('invalid');
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
const store = mockStore(state);
await store.dispatch(checkOnboardingStatus());
const actions = store.getActions();
expect(actions).toEqual([]);
expect(mockGetItem.mock.calls.length).toBe(1);
});
it('dispatches the correct action', async() => {
mockGetItem = jest.fn().mockReturnValue('1');
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
const store = mockStore(state);
await store.dispatch(checkOnboardingStatus());
const actions = store.getActions();
expect(actions).toEqual([{ type: 'ONBOARDING_START' }]);
expect(mockGetItem.mock.calls.length).toBe(1);
});
});
describe('startOnboarding()', () => {
let mockSetItem: any;
mockWindowProperty('localStorage', {
setItem: (key: string, value: string) => mockSetItem(key, value),
});
beforeEach(() => {
mockSetItem = jest.fn();
});
it('dispatches the correct action', async() => {
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
const store = mockStore(state);
await store.dispatch(startOnboarding());
const actions = store.getActions();
expect(actions).toEqual([{ type: 'ONBOARDING_START' }]);
expect(mockSetItem.mock.calls.length).toBe(1);
});
});
describe('endOnboarding()', () => {
let mockRemoveItem: any;
mockWindowProperty('localStorage', {
removeItem: (key: string) => mockRemoveItem(key),
});
beforeEach(() => {
mockRemoveItem = jest.fn();
});
it('dispatches the correct action', async() => {
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
const store = mockStore(state);
await store.dispatch(endOnboarding());
const actions = store.getActions();
expect(actions).toEqual([{ type: 'ONBOARDING_END' }]);
expect(mockRemoveItem.mock.calls.length).toBe(1);
});
});

Wyświetl plik

@ -1,8 +0,0 @@
import { changeSetting, saveSettings } from './settings';
export const INTRODUCTION_VERSION = 20181216044202;
export const closeOnboarding = () => dispatch => {
dispatch(changeSetting(['introductionVersion'], INTRODUCTION_VERSION));
dispatch(saveSettings());
};

Wyświetl plik

@ -0,0 +1,40 @@
const ONBOARDING_START = 'ONBOARDING_START';
const ONBOARDING_END = 'ONBOARDING_END';
const ONBOARDING_LOCAL_STORAGE_KEY = 'soapbox:onboarding';
type OnboardingStartAction = {
type: typeof ONBOARDING_START
}
type OnboardingEndAction = {
type: typeof ONBOARDING_END
}
export type OnboardingActions = OnboardingStartAction | OnboardingEndAction
const checkOnboardingStatus = () => (dispatch: React.Dispatch<OnboardingActions>) => {
const needsOnboarding = localStorage.getItem(ONBOARDING_LOCAL_STORAGE_KEY) === '1';
if (needsOnboarding) {
dispatch({ type: ONBOARDING_START });
}
};
const startOnboarding = () => (dispatch: React.Dispatch<OnboardingActions>) => {
localStorage.setItem(ONBOARDING_LOCAL_STORAGE_KEY, '1');
dispatch({ type: ONBOARDING_START });
};
const endOnboarding = () => (dispatch: React.Dispatch<OnboardingActions>) => {
localStorage.removeItem(ONBOARDING_LOCAL_STORAGE_KEY);
dispatch({ type: ONBOARDING_END });
};
export {
ONBOARDING_END,
ONBOARDING_START,
checkOnboardingStatus,
endOnboarding,
startOnboarding,
};

Wyświetl plik

@ -63,6 +63,23 @@ const customRender = (
...options,
});
const mockWindowProperty = (property: any, value: any) => {
const { [property]: originalProperty } = window;
delete window[property];
beforeAll(() => {
Object.defineProperty(window, property, {
configurable: true,
writable: true,
value,
});
});
afterAll(() => {
window[property] = originalProperty;
});
};
export * from '@testing-library/react';
export {
customRender as render,
@ -70,4 +87,5 @@ export {
applyActions,
rootState,
rootReducer,
mockWindowProperty,
};

Wyświetl plik

@ -0,0 +1,27 @@
import { ONBOARDING_START, ONBOARDING_END } from 'soapbox/actions/onboarding';
import reducer from '../onboarding';
describe('onboarding reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual({
needsOnboarding: false,
});
});
describe('ONBOARDING_START', () => {
it('sets "needsOnboarding" to "true"', () => {
const initialState = { needsOnboarding: false };
const action = { type: ONBOARDING_START };
expect(reducer(initialState, action).needsOnboarding).toEqual(true);
});
});
describe('ONBOARDING_END', () => {
it('sets "needsOnboarding" to "false"', () => {
const initialState = { needsOnboarding: true };
const action = { type: ONBOARDING_END };
expect(reducer(initialState, action).needsOnboarding).toEqual(false);
});
});
});

Wyświetl plik

@ -39,6 +39,7 @@ import meta from './meta';
import modals from './modals';
import mutes from './mutes';
import notifications from './notifications';
import onboarding from './onboarding';
import patron from './patron';
import pending_statuses from './pending_statuses';
import polls from './polls';
@ -118,6 +119,7 @@ const reducers = {
accounts_meta,
trending_statuses,
verification,
onboarding,
};
// Build a default state from all reducers: it has the key and `undefined`

Wyświetl plik

@ -0,0 +1,22 @@
import { ONBOARDING_START, ONBOARDING_END } from 'soapbox/actions/onboarding';
import type { OnboardingActions } from 'soapbox/actions/onboarding';
type OnboardingState = {
needsOnboarding: boolean,
}
const initialState: OnboardingState = {
needsOnboarding: false,
};
export default function onboarding(state: OnboardingState = initialState, action: OnboardingActions): OnboardingState {
switch(action.type) {
case ONBOARDING_START:
return { ...state, needsOnboarding: true };
case ONBOARDING_END:
return { ...state, needsOnboarding: false };
default:
return state;
}
}