feat: make editable code available in tests
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
6e091a7cdb
commit
68b223322f
@ -5,8 +5,10 @@ window.$ = jQuery;
|
||||
|
||||
document.__initTestFrame = initTestFrame;
|
||||
|
||||
async function initTestFrame(e = {}) {
|
||||
const code = (e.code || '').slice(0);
|
||||
async function initTestFrame(e = { code: {} }) {
|
||||
const code = (e.code.contents || '').slice();
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const editableContents = (e.code.editableContents || '').slice();
|
||||
if (!e.getUserInput) {
|
||||
e.getUserInput = () => code;
|
||||
}
|
||||
|
@ -55,7 +55,9 @@ const __utils = (() => {
|
||||
/* Run the test if there is one. If not just evaluate the user code */
|
||||
self.onmessage = async e => {
|
||||
/* eslint-disable no-unused-vars */
|
||||
const { code = '' } = e.data;
|
||||
const code = (e.data?.code?.contents || '').slice();
|
||||
const editableContents = (e.data?.code?.editableContents || '').slice();
|
||||
|
||||
const assert = chai.assert;
|
||||
// Fake Deep Equal dependency
|
||||
const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
||||
|
@ -481,6 +481,7 @@ class Editor extends Component {
|
||||
|
||||
outputNode.innerHTML = 'TESTS GO HERE';
|
||||
|
||||
// TODO: does it?
|
||||
// The z-index needs increasing as ViewZones default to below the lines.
|
||||
outputNode.style.zIndex = '10';
|
||||
|
||||
@ -508,7 +509,19 @@ class Editor extends Component {
|
||||
|
||||
onChange = editorValue => {
|
||||
const { updateFile } = this.props;
|
||||
updateFile({ key: this.state.fileKey, editorValue });
|
||||
// TODO: use fileKey everywhere?
|
||||
const { fileKey: key } = this.state;
|
||||
// TODO: now that we have getCurrentEditableRegion, should the overlays
|
||||
// follow that directly? We could subscribe to changes to that and redraw if
|
||||
// those imply that the positions have changed (i.e. if the content height
|
||||
// has changed or if content is dragged between regions)
|
||||
|
||||
const editableRegion = this.getCurrentEditableRegion(key);
|
||||
const editableRegionBoundaries = editableRegion && [
|
||||
editableRegion.startLineNumber - 1,
|
||||
editableRegion.endLineNumber + 1
|
||||
];
|
||||
updateFile({ key, editorValue, editableRegionBoundaries });
|
||||
};
|
||||
|
||||
changeTab = newFileKey => {
|
||||
@ -620,6 +633,48 @@ class Editor extends Component {
|
||||
).startLineNumber;
|
||||
}
|
||||
|
||||
translateRange = (range, lineDelta) => {
|
||||
const iRange = {
|
||||
...range,
|
||||
startLineNumber: range.startLineNumber + lineDelta,
|
||||
endLineNumber: range.endLineNumber + lineDelta
|
||||
};
|
||||
return this._monaco.Range.lift(iRange);
|
||||
};
|
||||
|
||||
getLinesBetweenRanges = (firstRange, secondRange) => {
|
||||
const startRange = this.translateRange(toLastLine(firstRange), 1);
|
||||
const endRange = this.translateRange(
|
||||
toStartOfLine(secondRange),
|
||||
-1
|
||||
).collapseToStart();
|
||||
|
||||
return {
|
||||
startLineNumber: startRange.startLineNumber,
|
||||
endLineNumber: endRange.endLineNumber
|
||||
};
|
||||
};
|
||||
|
||||
getCurrentEditableRegion = key => {
|
||||
const model = this.data[key].model;
|
||||
// TODO: this is a little low-level, but we should bail if there is no
|
||||
// editable region defined.
|
||||
if (!this.data[key].startEditDecId || !this.data[key].endEditDecId)
|
||||
return null;
|
||||
const firstRange = model.getDecorationRange(this.data[key].startEditDecId);
|
||||
const secondRange = model.getDecorationRange(this.data[key].endEditDecId);
|
||||
const { startLineNumber, endLineNumber } = this.getLinesBetweenRanges(
|
||||
firstRange,
|
||||
secondRange
|
||||
);
|
||||
|
||||
// getValueInRange includes column x if
|
||||
// startColumnNumber <= x < endColumnNumber
|
||||
// so we add 1 here
|
||||
const endColumn = model.getLineLength(endLineNumber) + 1;
|
||||
return new this._monaco.Range(startLineNumber, 1, endLineNumber, endColumn);
|
||||
};
|
||||
|
||||
decorateForbiddenRanges(key, editableRegion) {
|
||||
const model = this.data[key].model;
|
||||
const forbiddenRanges = [
|
||||
@ -674,15 +729,6 @@ class Editor extends Component {
|
||||
return newLines.map(({ range }) => range);
|
||||
}
|
||||
|
||||
const translateRange = (range, lineDelta) => {
|
||||
const iRange = {
|
||||
...range,
|
||||
startLineNumber: range.startLineNumber + lineDelta,
|
||||
endLineNumber: range.endLineNumber + lineDelta
|
||||
};
|
||||
return this._monaco.Range.lift(iRange);
|
||||
};
|
||||
|
||||
// TODO refactor this mess
|
||||
// TODO this listener needs to be replaced on reset.
|
||||
model.onDidChangeContent(e => {
|
||||
@ -730,9 +776,9 @@ class Editor extends Component {
|
||||
).collapseToStart();
|
||||
// the decoration needs adjusting if the user creates a line immediately
|
||||
// before the greyed out region...
|
||||
const lineOneRange = translateRange(startOfZone, -2);
|
||||
const lineOneRange = this.translateRange(startOfZone, -2);
|
||||
// or immediately after it
|
||||
const lineTwoRange = translateRange(startOfZone, -1);
|
||||
const lineTwoRange = this.translateRange(startOfZone, -1);
|
||||
|
||||
for (const lineRange of newLineRanges) {
|
||||
const shouldMoveZone = this._monaco.Range.areIntersectingOrTouching(
|
||||
@ -753,7 +799,7 @@ class Editor extends Component {
|
||||
).collapseToStart();
|
||||
// the decoration needs adjusting if the user creates a line immediately
|
||||
// before the editable region.
|
||||
const lineOneRange = translateRange(endOfZone, -1);
|
||||
const lineOneRange = this.translateRange(endOfZone, -1);
|
||||
|
||||
for (const lineRange of newLineRanges) {
|
||||
const shouldMoveZone = this._monaco.Range.areIntersectingOrTouching(
|
||||
@ -776,7 +822,7 @@ class Editor extends Component {
|
||||
// NOTE: any change in the decoration has already happened by this point
|
||||
// so this covers the *new* decoration range.
|
||||
const coveringRange = toStartOfLine(model.getDecorationRange(id));
|
||||
const oldStartOfRange = translateRange(
|
||||
const oldStartOfRange = this.translateRange(
|
||||
coveringRange.collapseToStart(),
|
||||
1
|
||||
);
|
||||
|
@ -323,7 +323,9 @@ export default connect(
|
||||
mapDispatchToProps
|
||||
)(ShowClassic);
|
||||
|
||||
// TODO: handle jsx (not sure why it doesn't get an editableRegion)
|
||||
// TODO: handle jsx (not sure why it doesn't get an editableRegion) EDIT:
|
||||
// probably because the dummy challenge didn't include it, so Gatsby couldn't
|
||||
// infer it.
|
||||
export const query = graphql`
|
||||
query ClassicChallenge($slug: String!) {
|
||||
challengeNode(fields: { slug: { eq: $slug } }) {
|
||||
|
@ -138,7 +138,13 @@ function loadCodeEpic(action$, state$) {
|
||||
...file,
|
||||
contents: codeFound[file.key]
|
||||
? codeFound[file.key].contents
|
||||
: file.contents
|
||||
: file.contents,
|
||||
editableContents: codeFound[file.key]
|
||||
? codeFound[file.key].editableContents
|
||||
: file.editableContents,
|
||||
editableRegionBoundaries: codeFound[file.key]
|
||||
? codeFound[file.key].editableRegionBoundaries
|
||||
: file.editableRegionBoundaries
|
||||
}
|
||||
}),
|
||||
{}
|
||||
|
@ -116,14 +116,27 @@ export const createFiles = createAction(types.createFiles, challengeFiles =>
|
||||
...challengeFiles,
|
||||
[file.key]: {
|
||||
...createPoly(file),
|
||||
seed: file.contents.slice(0),
|
||||
editableRegion: file.editableRegion
|
||||
seed: file.contents.slice(),
|
||||
editableContents: getLines(
|
||||
file.contents,
|
||||
file.editableRegionBoundaries
|
||||
),
|
||||
seedEditableRegionBoundaries: file.editableRegionBoundaries.slice()
|
||||
}
|
||||
}),
|
||||
{}
|
||||
)
|
||||
);
|
||||
|
||||
// TODO: secure with tests
|
||||
function getLines(contents, range) {
|
||||
const lines = contents.split('\n');
|
||||
const editableLines = isEmpty(lines)
|
||||
? []
|
||||
: lines.slice(range[0], range[1] - 1);
|
||||
return editableLines.join('\n');
|
||||
}
|
||||
|
||||
export const createQuestion = createAction(types.createQuestion);
|
||||
export const initTests = createAction(types.initTests);
|
||||
export const updateTests = createAction(types.updateTests);
|
||||
@ -251,13 +264,18 @@ export const reducer = handleActions(
|
||||
...state,
|
||||
challengeFiles: payload
|
||||
}),
|
||||
[types.updateFile]: (state, { payload: { key, editorValue } }) => ({
|
||||
[types.updateFile]: (
|
||||
state,
|
||||
{ payload: { key, editorValue, editableRegionBoundaries } }
|
||||
) => ({
|
||||
...state,
|
||||
challengeFiles: {
|
||||
...state.challengeFiles,
|
||||
[key]: {
|
||||
...state.challengeFiles[key],
|
||||
contents: editorValue
|
||||
contents: editorValue,
|
||||
editableContents: getLines(editorValue, editableRegionBoundaries),
|
||||
editableRegionBoundaries
|
||||
}
|
||||
}
|
||||
}),
|
||||
@ -265,7 +283,6 @@ export const reducer = handleActions(
|
||||
...state,
|
||||
challengeFiles: payload
|
||||
}),
|
||||
|
||||
[types.initTests]: (state, { payload }) => ({
|
||||
...state,
|
||||
challengeTests: payload
|
||||
@ -314,7 +331,11 @@ export const reducer = handleActions(
|
||||
[file.key]: {
|
||||
...file,
|
||||
contents: file.seed.slice(),
|
||||
editableRegion: file.editableRegion
|
||||
editableContents: getLines(
|
||||
file.seed,
|
||||
file.seedEditableRegionBoundaries
|
||||
),
|
||||
editableRegionBoundaries: file.seedEditableRegionBoundaries
|
||||
}
|
||||
}),
|
||||
{}
|
||||
|
@ -53,13 +53,15 @@ function buildSourceMap(files) {
|
||||
// the same name 'index'. This made the last file the only file to appear in
|
||||
// sources.
|
||||
// A better solution is to store and handle them separately. Perhaps never
|
||||
// setting the name to 'index'.
|
||||
// setting the name to 'index'. Use 'contents' instead?
|
||||
// TODO: is file.source ever defined?
|
||||
return files.reduce(
|
||||
(sources, file) => {
|
||||
sources[file.name] += file.source || file.contents;
|
||||
sources.editableContents += file.editableContents;
|
||||
return sources;
|
||||
},
|
||||
{ index: '' }
|
||||
{ index: '', editableContents: '' }
|
||||
);
|
||||
}
|
||||
|
||||
@ -111,7 +113,10 @@ export function getTestRunner(buildData, { proxyLogger }, document) {
|
||||
}
|
||||
|
||||
function getJSTestRunner({ build, sources }, proxyLogger) {
|
||||
const code = sources && 'index' in sources ? sources['index'] : '';
|
||||
const code = {
|
||||
contents: sources.index,
|
||||
editableContents: sources.editableContents
|
||||
};
|
||||
|
||||
const testWorker = createWorker(testEvaluator, { terminateWorker: true });
|
||||
|
||||
|
@ -98,7 +98,10 @@ const initTestFrame = frameReady => ctx => {
|
||||
const { sources, loadEnzyme } = ctx;
|
||||
// default for classic challenges
|
||||
// should not be used for modern
|
||||
const code = sources && 'index' in sources ? sources['index'] : '';
|
||||
const code = {
|
||||
contents: sources.index,
|
||||
editableContents: sources.editableContents
|
||||
};
|
||||
// provide the file name and get the original source
|
||||
const getUserInput = fileName => toString(sources[fileName]);
|
||||
await ctx.document.__initTestFrame({ code, getUserInput, loadEnzyme });
|
||||
|
@ -432,7 +432,10 @@ async function createTestRunner(challenge, solution, buildChallenge) {
|
||||
required,
|
||||
template
|
||||
});
|
||||
const code = sources && 'index' in sources ? sources['index'] : '';
|
||||
const code = {
|
||||
contents: sources.index,
|
||||
editableContents: sources.editableContents
|
||||
};
|
||||
|
||||
const evaluator = await (buildChallenge === buildDOMChallenge
|
||||
? getContextEvaluator(build, sources, code, loadEnzyme)
|
||||
|
Reference in New Issue
Block a user