Added tests
This commit is contained in:
parent
e8949a1ee4
commit
0ef8585726
6 changed files with 265 additions and 78 deletions
80
app/assets/javascripts/blob/notebook/index.js
Normal file
80
app/assets/javascripts/blob/notebook/index.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
/* eslint-disable no-new */
|
||||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
import NotebookLab from 'vendor/notebooklab';
|
||||
|
||||
Vue.use(VueResource);
|
||||
Vue.use(NotebookLab);
|
||||
|
||||
export default () => {
|
||||
const el = document.getElementById('js-notebook-viewer');
|
||||
|
||||
new Vue({
|
||||
el,
|
||||
data() {
|
||||
return {
|
||||
error: false,
|
||||
loadError: false,
|
||||
loading: true,
|
||||
json: {},
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div class="container-fluid md prepend-top-default append-bottom-default">
|
||||
<div
|
||||
class="text-center loading"
|
||||
v-if="loading && !error">
|
||||
<i
|
||||
class="fa fa-spinner fa-spin"
|
||||
aria-hidden="true"
|
||||
aria-label="iPython notebook loading">
|
||||
</i>
|
||||
</div>
|
||||
<notebook-lab
|
||||
v-if="!loading && !error"
|
||||
:notebook="json" />
|
||||
<p
|
||||
class="text-center"
|
||||
v-if="error">
|
||||
<span v-if="loadError">
|
||||
An error occured whilst loading the file. Please try again later.
|
||||
</span>
|
||||
<span v-else>
|
||||
An error occured whilst parsing the file.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
methods: {
|
||||
loadFile() {
|
||||
this.$http.get(el.dataset.endpoint)
|
||||
.then((res) => {
|
||||
this.json = res.json();
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.status) {
|
||||
this.loadError = true;
|
||||
}
|
||||
|
||||
this.error = true;
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
$('<link>', {
|
||||
rel: 'stylesheet',
|
||||
type: 'text/css',
|
||||
href: gon.katex_css_url,
|
||||
}).appendTo('head');
|
||||
|
||||
if (gon.katex_js_url) {
|
||||
$.getScript(gon.katex_js_url, () => {
|
||||
this.loadFile();
|
||||
});
|
||||
} else {
|
||||
this.loadFile();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,75 +1,3 @@
|
|||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
import NotebookLab from 'vendor/notebooklab';
|
||||
import renderNotebook from './notebook';
|
||||
|
||||
Vue.use(VueResource);
|
||||
Vue.use(NotebookLab);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const el = document.getElementById('js-notebook-viewer');
|
||||
|
||||
new Vue({
|
||||
el,
|
||||
data() {
|
||||
return {
|
||||
error: false,
|
||||
loadError: false,
|
||||
loading: true,
|
||||
json: {},
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div class="container-fluid md prepend-top-default append-bottom-default">
|
||||
<div
|
||||
class="text-center loading"
|
||||
v-if="loading && !error">
|
||||
<i
|
||||
class="fa fa-spinner fa-spin"
|
||||
aria-hidden="true"
|
||||
aria-label="iPython notebook loading">
|
||||
</i>
|
||||
</div>
|
||||
<notebook-lab
|
||||
v-if="!loading && !error"
|
||||
:notebook="json" />
|
||||
<p
|
||||
class="text-center"
|
||||
v-if="error">
|
||||
<span v-if="loadError">
|
||||
An error occured whilst loading the file. Please try again later.
|
||||
</span>
|
||||
<span v-else>
|
||||
An error occured whilst parsing the file.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
methods: {
|
||||
loadFile() {
|
||||
this.$http.get(el.dataset.endpoint)
|
||||
.then((res) => {
|
||||
this.json = res.json();
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.status) {
|
||||
this.loadError = true;
|
||||
}
|
||||
|
||||
this.error = true;
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
$('<link>', {
|
||||
rel: 'stylesheet',
|
||||
type: 'text/css',
|
||||
href: gon.katex_css_url,
|
||||
}).appendTo('head');
|
||||
|
||||
$.getScript(gon.katex_js_url, () => {
|
||||
this.loadFile();
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', renderNotebook);
|
||||
|
|
159
spec/javascripts/blob/notebook/index_spec.js
Normal file
159
spec/javascripts/blob/notebook/index_spec.js
Normal file
|
@ -0,0 +1,159 @@
|
|||
import Vue from 'vue';
|
||||
import renderNotebook from '~/blob/notebook';
|
||||
|
||||
describe('iPython notebook renderer', () => {
|
||||
preloadFixtures('static/notebook_viewer.html.raw');
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures('static/notebook_viewer.html.raw');
|
||||
});
|
||||
|
||||
it('shows loading icon', () => {
|
||||
renderNotebook();
|
||||
|
||||
expect(
|
||||
document.querySelector('.loading'),
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
describe('successful response', () => {
|
||||
const response = (request, next) => {
|
||||
next(request.respondWith(JSON.stringify({
|
||||
cells: [{
|
||||
cell_type: 'markdown',
|
||||
source: ['# test'],
|
||||
}, {
|
||||
cell_type: 'code',
|
||||
execution_count: 1,
|
||||
source: [
|
||||
'def test(str)',
|
||||
' return str',
|
||||
],
|
||||
outputs: [],
|
||||
}],
|
||||
}), {
|
||||
status: 200,
|
||||
}));
|
||||
};
|
||||
|
||||
beforeEach((done) => {
|
||||
Vue.http.interceptors.push(response);
|
||||
|
||||
renderNotebook();
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Vue.http.interceptors = _.without(
|
||||
Vue.http.interceptors, response,
|
||||
);
|
||||
});
|
||||
|
||||
it('does not show loading icon', () => {
|
||||
expect(
|
||||
document.querySelector('.loading'),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('renders the notebook', () => {
|
||||
expect(
|
||||
document.querySelector('.md'),
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
it('renders the markdown cell', () => {
|
||||
expect(
|
||||
document.querySelector('h1'),
|
||||
).not.toBeNull();
|
||||
|
||||
expect(
|
||||
document.querySelector('h1').textContent.trim(),
|
||||
).toBe('test');
|
||||
});
|
||||
|
||||
it('highlights code', () => {
|
||||
expect(
|
||||
document.querySelector('.hljs'),
|
||||
).not.toBeNull();
|
||||
|
||||
expect(
|
||||
document.querySelector('.python'),
|
||||
).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('error in JSON response', () => {
|
||||
const response = (request, next) => {
|
||||
next(request.respondWith('{ "cells": [{"cell_type": "markdown"} }', {
|
||||
status: 200,
|
||||
}));
|
||||
};
|
||||
|
||||
beforeEach((done) => {
|
||||
Vue.http.interceptors.push(response);
|
||||
|
||||
renderNotebook();
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Vue.http.interceptors = _.without(
|
||||
Vue.http.interceptors, response,
|
||||
);
|
||||
});
|
||||
|
||||
it('does not show loading icon', () => {
|
||||
expect(
|
||||
document.querySelector('.loading'),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('shows error message', () => {
|
||||
expect(
|
||||
document.querySelector('.md').textContent.trim(),
|
||||
).toBe('An error occured whilst parsing the file.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('error getting file', () => {
|
||||
const response = (request, next) => {
|
||||
next(request.respondWith('', {
|
||||
status: 500,
|
||||
}));
|
||||
};
|
||||
|
||||
beforeEach((done) => {
|
||||
Vue.http.interceptors.push(response);
|
||||
|
||||
renderNotebook();
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Vue.http.interceptors = _.without(
|
||||
Vue.http.interceptors, response,
|
||||
);
|
||||
});
|
||||
|
||||
it('does not show loading icon', () => {
|
||||
expect(
|
||||
document.querySelector('.loading'),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('shows error message', () => {
|
||||
expect(
|
||||
document.querySelector('.md').textContent.trim(),
|
||||
).toBe('An error occured whilst loading the file. Please try again later.');
|
||||
});
|
||||
});
|
||||
});
|
1
spec/javascripts/fixtures/notebook_viewer.html.haml
Normal file
1
spec/javascripts/fixtures/notebook_viewer.html.haml
Normal file
|
@ -0,0 +1 @@
|
|||
.file-content#js-notebook-viewer{ data: { endpoint: '/test' } }
|
|
@ -53,6 +53,20 @@ describe Blob do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#ipython_notebook?' do
|
||||
it 'is falsey when language is not Jupyter Notebook' do
|
||||
git_blob = double(text?: true, language: double(name: 'JSON'))
|
||||
|
||||
expect(described_class.decorate(git_blob)).not_to be_ipython_notebook
|
||||
end
|
||||
|
||||
it 'is truthy when language is Jupyter Notebook' do
|
||||
git_blob = double(text?: true, language: double(name: 'Jupyter Notebook'))
|
||||
|
||||
expect(described_class.decorate(git_blob)).to be_ipython_notebook
|
||||
end
|
||||
end
|
||||
|
||||
describe '#video?' do
|
||||
it 'is falsey with image extension' do
|
||||
git_blob = Gitlab::Git::Blob.new(name: 'image.png')
|
||||
|
@ -116,6 +130,11 @@ describe Blob do
|
|||
blob = stubbed_blob
|
||||
expect(blob.to_partial_path(project)).to eq 'download'
|
||||
end
|
||||
|
||||
it 'handles iPython notebooks' do
|
||||
blob = stubbed_blob(text?: true, ipython_notebook?: true)
|
||||
expect(blob.to_partial_path(project)).to eq 'notebook'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#size_within_svg_limits?' do
|
||||
|
|
8
vendor/assets/javascripts/notebooklab.js
vendored
8
vendor/assets/javascripts/notebooklab.js
vendored
|
@ -414,13 +414,13 @@ exports.default = {
|
|||
},
|
||||
computed: {
|
||||
markdown: function markdown() {
|
||||
var regex = new RegExp(/^\$\$(.*)\$\$$/, 'g');
|
||||
var regex = new RegExp('^\$\$(.*)\$\$$', 'g');
|
||||
|
||||
var source = this.cell.source.map(function (line) {
|
||||
var matches = regex.exec(line.trim());
|
||||
|
||||
// Only render use the Katex library if it is actually loaded
|
||||
if (matches && matches.length > 0 && katex) {
|
||||
if (matches && matches.length > 0 && typeof katex !== 'undefined') {
|
||||
return katex.renderToString(matches[1]);
|
||||
} else {
|
||||
return line;
|
||||
|
@ -3047,7 +3047,7 @@ function escape(html, encode) {
|
|||
}
|
||||
|
||||
function unescape(html) {
|
||||
// explicitly match decimal, hex, and named HTML entities
|
||||
// explicitly match decimal, hex, and named HTML entities
|
||||
return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/g, function(_, n) {
|
||||
n = n.toLowerCase();
|
||||
if (n === 'colon') return ':';
|
||||
|
@ -3636,4 +3636,4 @@ module.exports = {
|
|||
|
||||
/***/ })
|
||||
/******/ ]);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue