* feat(seed): Add modern challenge * chore(react): Use prop-types package * feat: Initial refactor to redux-first-router BREAKING CHANGE: Everything is different! * feat: First rendering * feat(routes): Challenges view render but failing * fix(Challenges): Remove contain HOC * fix(RFR): Add params selector * fix(RFR): :en should be :lang * fix: Update berks utils for redux * fix(Map): Challenge link to arg * fix(Map): Add trailing slash to map page * fix(RFR): Use FCC Link Use fcc Link to get around issue of lang being undefined * fix(Router): Link to is required * fix(app): Rely on RFR state for app lang * chore(RFR): Remove unused RFR Link * fix(RFR): Hydrate initial challenge using RFR and RO * fix: Casing issue * fix(RFR): Undefined links * fix(RFR): Use onRoute<name> convention for route types * feat(server/react): Add helpful redux logging/throwing * fix(server/react): Strip out nonjson from state This prevents thunks in routesMap from breaking serialization * fix(RFR/Link): Should accept any renderable * fix(RFR): Get redirects working * fix(RFR): Redirects and not found's * fix(Map): Move challenge onClick handler * fix(Map): Allow Router.link to handle clicks after onClick * fix(routes): Remove react-router-redux * feat(Router): Add lang to all route actions by default * fix(entities): Only fetch challenge if not already loaded * fix(Files): Move files to own feature * chore(Challenges): Remove vestigial hints logic * fix(RFR): Update challenges on route challenges * fix(code-storage): Should use events instead of commands * fix(Map): ClickOnMap should not hold on to event * chore(lint): Use eslint-config-freecodecamp Closes #15938 * feat(Panes): Update panes on route instead of render * fix(Panes): Store panesmap and update on fetchchallenges * fix(Panes): Normalize panesmaps * fix(Panes): Remove filter from createpanemap * fix(Panes): Middleware on location meta object * feat(Panes): Filter preview on nonhtml challenges * build(babel): Add lodash babel plugin * chore(lint): Lint js files * fix(server/user-stats): Remove use of lodash chain this interferes with babel-plugin-lodash * feat(dev): Add remote redux devtools for ssr * fix(Panes): Dispatch mount action this is needed to trigger window/divider epics * fix(Panes): Getpane to use new panesmap format * fix(Panes): Always update panes after state this lets the panes logic be affected by changes in state
147 lines
4.1 KiB
JavaScript
147 lines
4.1 KiB
JavaScript
import { Observable, config } from 'rx';
|
|
import test from 'tape';
|
|
import proxy from 'proxyquire';
|
|
import sinon from 'sinon';
|
|
|
|
import ns from '../ns.json';
|
|
// import challenges.redux to get around
|
|
// circular dependency
|
|
import { types as challenge } from '../../../redux';
|
|
import { types } from './';
|
|
|
|
config.longStackSupport = true;
|
|
const challengeSelectorStub = {};
|
|
const stepChallengeEpic = proxy(
|
|
'./step-challenge-epic',
|
|
{ '../../../../../redux': challengeSelectorStub }
|
|
);
|
|
|
|
const file = 'common/app/routes/Challenges/redux/step-challenge-epic';
|
|
test(file, function(t) {
|
|
t.test('does not respond to random actions', t => {
|
|
const actions = Observable.of({ type: 'NotTheMomma' });
|
|
let called = false;
|
|
stepChallengeEpic(actions, () => {})
|
|
.subscribe(
|
|
() => { called = true; },
|
|
e => t.fail(e),
|
|
() => {
|
|
if (!called) {
|
|
t.pass();
|
|
} else {
|
|
t.fail(new Error('epic should not respond'));
|
|
}
|
|
t.end();
|
|
}
|
|
);
|
|
});
|
|
t.test('steps back', t => {
|
|
const actions = Observable.of({ type: types.stepBackward });
|
|
const state = {
|
|
[ns]: {
|
|
currentIndex: 1,
|
|
unlockedSteps: [ true, undefined ] // eslint-disable-line no-undefined
|
|
}
|
|
};
|
|
const onNextSpy = sinon.spy();
|
|
challengeSelectorStub.challengeSelector = sinon.spy(_state => {
|
|
t.assert(_state === state, 'challenge selector not called with state');
|
|
return {
|
|
description: new Array(2)
|
|
};
|
|
});
|
|
stepChallengeEpic(actions, { getState: () => state })
|
|
.subscribe(
|
|
onNextSpy,
|
|
e => {
|
|
throw e;
|
|
},
|
|
() => {
|
|
t.assert(
|
|
onNextSpy.calledOnce,
|
|
'epic not called exactly once'
|
|
);
|
|
t.assert(
|
|
onNextSpy.calledWithMatch({
|
|
type: types.goToStep,
|
|
payload: { step: 0, isUnlocked: true }
|
|
}),
|
|
'Epic did not return the expected action'
|
|
);
|
|
delete challengeSelectorStub.challengeSelector;
|
|
t.end();
|
|
}
|
|
);
|
|
});
|
|
t.test('steps forward', t => {
|
|
const actions = Observable.of({ type: types.stepForward });
|
|
const state = {
|
|
[ns]: {
|
|
currentIndex: 0,
|
|
unlockedSteps: []
|
|
}
|
|
};
|
|
const onNextSpy = sinon.spy();
|
|
challengeSelectorStub.challengeSelector = sinon.spy(_state => {
|
|
t.assert(_state === state, 'challenge selector not called with state');
|
|
return {
|
|
description: new Array(2)
|
|
};
|
|
});
|
|
stepChallengeEpic(actions, { getState: () => state })
|
|
.subscribe(
|
|
onNextSpy,
|
|
e => {
|
|
throw e;
|
|
},
|
|
() => {
|
|
t.assert(
|
|
onNextSpy.calledOnce,
|
|
'epic not called exactly once'
|
|
);
|
|
t.assert(
|
|
onNextSpy.calledWithMatch({
|
|
type: types.goToStep,
|
|
payload: { step: 1, isUnlocked: false }
|
|
}),
|
|
'Epic did not return the expected action'
|
|
);
|
|
delete challengeSelectorStub.challengeSelector;
|
|
t.end();
|
|
}
|
|
);
|
|
});
|
|
t.test('submits on last step forward', t => {
|
|
const actions = Observable.of({ type: types.stepForward });
|
|
const state = { [ns]: { currentIndex: 1 } };
|
|
const onNextSpy = sinon.spy();
|
|
challengeSelectorStub.challengeSelector = sinon.spy(_state => {
|
|
t.assert(_state === state, 'challenge selector not called with state');
|
|
return {
|
|
description: new Array(2)
|
|
};
|
|
});
|
|
stepChallengeEpic(actions, { getState: () => state })
|
|
.subscribe(
|
|
onNextSpy,
|
|
e => {
|
|
throw e;
|
|
},
|
|
() => {
|
|
t.assert(
|
|
onNextSpy.calledOnce,
|
|
'epic not called exactly once'
|
|
);
|
|
t.assert(
|
|
onNextSpy.calledWithMatch({
|
|
type: challenge.submitChallenge.toString()
|
|
}),
|
|
'Epic did not return the expected action'
|
|
);
|
|
delete challengeSelectorStub.challengeSelector;
|
|
t.end();
|
|
}
|
|
);
|
|
});
|
|
});
|