Merge pull request #15006 from overleaf/mj-bad-karma

[web] Remove karma tests and config

GitOrigin-RevId: a157f46d1ecf93fcbc99713a1c3aa11f82e5a619
This commit is contained in:
Jakob Ackermann 2023-10-13 11:13:03 +02:00 committed by Copybot
parent 3e34467e12
commit 6b3dac803d
13 changed files with 7 additions and 2181 deletions

998
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,13 +0,0 @@
diff --git a/node_modules/finalhandler/index.js b/node_modules/finalhandler/index.js
index 5673507..40f4684 100644
--- a/node_modules/finalhandler/index.js
+++ b/node_modules/finalhandler/index.js
@@ -125,7 +125,7 @@ function finalhandler (req, res, options) {
// cannot actually respond
if (headersSent(res)) {
debug('cannot %d after headers sent', status)
- req.socket.destroy()
+ if (req.socket) req.socket.destroy()
return
}

View file

@ -37,7 +37,6 @@
"**/app/src/**/*.js",
"app.js",
"i18next-scanner.config.js",
"karma.conf.js",
"scripts/**/*.js",
"webpack.config*.js"
],
@ -82,14 +81,6 @@
"mocha/prefer-arrow-callback": "error"
}
},
{
// Frontend test specific rules
"files": ["**/test/karma/**/*.js"],
"globals": {
"expect": true,
"$": true
}
},
{
// Backend specific rules
"files": ["**/app/src/**/*.js", "app.js"],

View file

@ -45,8 +45,6 @@ clean:
-COMPOSE_PROJECT_NAME=acceptance_modules_merged_saas_4_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down --rmi local
-COMPOSE_PROJECT_NAME=acceptance_modules_merged_server_ce_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down --rmi local
-COMPOSE_PROJECT_NAME=acceptance_modules_merged_server_pro_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down --rmi local
-COMPOSE_PROJECT_NAME=karma_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down --rmi local
-COMPOSE_PROJECT_NAME=karma_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down --rmi local
-COMPOSE_PROJECT_NAME=frontend_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down --rmi local
-COMPOSE_PROJECT_NAME=tar_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down --rmi local
@ -60,7 +58,7 @@ clean_ci:
# Tests
#
test: test_unit test_karma test_acceptance test_frontend test_frontend_ct
test: test_unit test_acceptance test_frontend test_frontend_ct
test_module: test_unit_module test_acceptance_module
@ -115,18 +113,6 @@ test_unit_modules: $(TEST_UNIT_MODULES)
test_unit_module:
$(MAKE) modules/$(MODULE_NAME)/test_unit
#
# Karma frontend tests
#
test_karma: build_test_karma test_karma_run
test_karma_run:
COMPOSE_PROJECT_NAME=karma_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down -v -t 0
COMPOSE_PROJECT_NAME=karma_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) run --rm test_karma
COMPOSE_PROJECT_NAME=karma_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down -v -t 0
test_karma_build_run: build_test_karma test_karma_run
#
# Frontend tests
@ -445,7 +431,7 @@ lint_test_modules:
--max-warnings=0
lint: lint_misc
# migrations, scripts, webpack config, karma config
# migrations, scripts, webpack config
lint_misc:
npx eslint . \
--ignore-pattern 'app.{js,jsx,mjs,ts,tsx}' \
@ -558,9 +544,6 @@ build:
--file Dockerfile \
../..
build_test_karma:
COMPOSE_PROJECT_NAME=karma_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) build test_karma
publish:
docker push $(DOCKER_REPO)/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER)
@ -583,7 +566,7 @@ $(MODULE_TARGETS):
$(MODULE_TARGETS) \
compile_modules compile_modules_full clean_ci \
test test_module test_unit test_unit_app \
test_unit_modules test_unit_module test_karma test_karma_run \
test_karma_build_run test_frontend test_acceptance test_acceptance_app \
test_acceptance_modules test_acceptance_module ci format format_fix lint \
build build_test_karma publish tar
test_unit_modules test_unit_module test_frontend \
test_acceptance test_acceptance_app test_acceptance_modules \
test_acceptance_module ci format format_fix lint \
build publish tar

View file

@ -38,20 +38,6 @@ services:
- saml
- ldap
test_karma:
build:
context: ../..
dockerfile: services/web/Dockerfile.frontend.ci
args:
PROJECT_NAME: $PROJECT_NAME
BRANCH_NAME: $BRANCH_NAME
BUILD_NUMBER: $BUILD_NUMBER
working_dir: /overleaf/services/web
command: npm run test:karma:single
user: node
environment:
NODE_OPTIONS: "--unhandled-rejections=strict"
test_frontend:
build:
context: ../..

View file

@ -46,19 +46,6 @@ services:
- ldap
command: npm run --silent test:acceptance:app
test_karma:
build:
context: ../..
dockerfile: services/web/Dockerfile.frontend
volumes:
- .:/overleaf/services/web
- ../../node_modules:/overleaf/node_modules
- ../../libraries:/overleaf/libraries
environment:
NODE_OPTIONS: "--unhandled-rejections=strict"
working_dir: /overleaf/services/web
command: npm run --silent test:karma:single
test_frontend:
build:
context: ../..

View file

@ -1,65 +0,0 @@
const webpackConfig = require('./webpack.config.test')
module.exports = function (config) {
config.set({
customLaunchers: {
ChromeCustom: {
base: 'ChromeHeadless',
// We must disable the Chrome sandbox when running Chrome inside Docker
// (Chrome's sandbox needs more permissions than Docker allows by
// default)
flags: ['--no-sandbox'],
},
},
browsers: ['ChromeCustom'],
files: [
// Import all tests (see comment in the file for why this is necessary)
'test/karma/import_tests.js',
],
middleware: ['fake-img'],
preprocessors: {
// Run files through webpack
'test/karma/import_tests.js': ['webpack'],
},
frameworks: ['mocha', 'chai-sinon'],
// Configure webpack in the tests
webpack: webpackConfig,
// Configure the webpack dev server used to serve test files
webpackMiddleware: {
// Disable file-watching -- it is of no use in CI, we use single runs.
// https://webpack.js.org/configuration/watch/
watch: false,
// ^ does not work when placed in webpack.config.test.
// webpack-dev-middleware overrides it :/
// v seems to be supported, according to
// https://www.npmjs.com/package/webpack-dev-middleware#watchoptions
watchOptions: {
ignored: [/node_modules/, /frontend/, /test/],
},
// Disable noisy CLI output
stats: 'errors-only',
},
plugins: [
require('karma-chrome-launcher'),
require('karma-mocha'),
require('karma-chai-sinon'),
require('karma-webpack'),
require('karma-mocha-reporter'),
{ 'middleware:fake-img': ['factory', fakeImgMiddlewareFactory] },
],
reporters: ['mocha'],
})
}
/**
* Handle fake images
*/
function fakeImgMiddlewareFactory() {
return function (req, res, next) {
if (req.originalUrl.startsWith('/fake/')) {
return res.end('fake img response')
}
next()
}
}

View file

@ -15,8 +15,6 @@
"test:unit:app": "npm run test:unit:run_dir -- test/unit/src",
"test:frontend": "NODE_ENV=test TZ=GMT mocha --recursive --timeout 5000 --exit --extension js,jsx,mjs,ts,tsx --grep=$MOCHA_GREP --require test/frontend/bootstrap.js --ignore '**/*.spec.{js,jsx,ts,tsx}' test/frontend modules/*/test/frontend",
"test:frontend:coverage": "c8 --all --include 'frontend/js' --include 'modules/*/frontend/js' --exclude 'frontend/js/vendor' --reporter=lcov --reporter=text-summary npm run test:frontend",
"test:karma": "karma start",
"test:karma:single": "karma start --no-auto-watch --single-run",
"start": "node $NODE_APP_OPTIONS app.js",
"nodemon": "node --watch $NODE_APP_OPTIONS app.js --watch-locales",
"webpack": "webpack serve --config webpack.config.dev.js",
@ -58,7 +56,7 @@
"local:test:acceptance": "npm run local:test:acceptance:app && npm run local:test:acceptance:modules",
"local:test:unit": "npm run test:unit:all",
"local:test:frontend": "npm run test:frontend",
"local:test": "npm run local:test:unit && npm run local:test:frontend && npm run test:karma:single && npm run local:test:acceptance"
"local:test": "npm run local:test:unit && npm run local:test:frontend && npm run local:test:acceptance"
},
"browserslist": [
"last 1 year",
@ -289,13 +287,6 @@
"jquery": "^2.2.4",
"jsdom": "^19.0.0",
"jsdom-global": "^3.0.2",
"karma": "^6.3.17",
"karma-chai-sinon": "^0.1.5",
"karma-chrome-launcher": "^3.1.0",
"karma-mocha": "^2.0.0",
"karma-mocha-reporter": "^2.2.5",
"karma-requirejs": "^1.1.0",
"karma-webpack": "^5.0.0",
"less": "^3.11.1",
"less-loader": "^11.1.3",
"match-sorter": "^6.2.0",

View file

@ -1,308 +0,0 @@
/* eslint-disable
max-len,
*/
/* global inject, sinon */
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import SpellCheckManager from '../../../../../../frontend/js/ide/editor/directives/aceEditor/spell-check/SpellCheckManager'
export default describe('SpellCheckManager', function () {
beforeEach(function (done) {
this.timelord = sinon.useFakeTimers()
window.user = { id: 1 }
window.csrfToken = 'token'
this.scope = {
$watch: sinon.stub(),
spellCheck: true,
spellCheckLanguage: 'en',
}
this.highlightedWordManager = {
reset: sinon.stub(),
clearRow: sinon.stub(),
addHighlight: sinon.stub(),
}
this.adapter = {
getLineCount: sinon.stub(),
getFirstVisibleRowNum: sinon.stub(),
getLastVisibleRowNum: sinon.stub(),
getLinesByRows: sinon.stub(),
highlightedWordManager: this.highlightedWordManager,
}
return inject(($q, $http, $httpBackend, $cacheFactory) => {
this.$http = $http
this.$q = $q
this.$httpBackend = $httpBackend
this.spellCheckManager = new SpellCheckManager(
this.scope,
$cacheFactory,
$http,
$q,
this.adapter
)
return done()
})
})
afterEach(function () {
return this.timelord.restore()
})
it('adds an highlight when a misspelling is found', function () {
this.$httpBackend.when('POST', '/spelling/check').respond({
misspellings: [
{
index: 0,
suggestions: ['opposition'],
},
],
})
this.adapter.getLinesByRows.returns(['oppozition'])
this.spellCheckManager.init()
this.timelord.tick(500)
this.$httpBackend.flush()
expect(this.highlightedWordManager.addHighlight).to.have.been.called
})
describe('runSpellCheck', function () {
beforeEach(function () {
this.adapter.getLineCount.returns(10)
this.adapter.getFirstVisibleRowNum.returns(3)
this.adapter.getLastVisibleRowNum.returns(5)
this.adapter.getLinesByRows.returns([
'Lorem ipsum dolor sit amet',
'consectetur adipisicing elit',
'sed do eiusmod',
])
this.$httpBackend.when('POST', '/spelling/check').respond({
misspellings: [
{
index: 0,
suggestions: ['opposition'],
},
],
})
})
describe('when doing the first check', function () {
beforeEach(function () {
this.spellCheckManager.init()
})
it('initially flags all lines as dirty ', function () {
expect(this.spellCheckManager.changedLines)
.to.have.lengthOf(10)
.and.to.not.include(false)
})
it('checks beyond the currently visible viewport', function () {
this.timelord.tick(500)
this.$httpBackend.flush()
expect(this.adapter.getLinesByRows).to.have.been.calledWith([
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
])
})
})
describe('after the initial check', function () {
beforeEach(function () {
this.spellCheckManager.init()
this.spellCheckManager.firstCheck = false
})
it('only checks visible lines', function () {
this.spellCheckManager.runSpellCheck()
this.spellCheckManager.timeoutId = null
this.$httpBackend.flush()
expect(this.adapter.getLinesByRows).to.have.been.calledWith([3, 4, 5])
})
it('flags checked lines as non-dirty', function () {
this.spellCheckManager.runSpellCheck()
this.spellCheckManager.timeoutId = null
this.$httpBackend.flush()
expect(this.spellCheckManager.changedLines[2]).to.equal(true)
expect(this.spellCheckManager.changedLines[3]).to.equal(false)
expect(this.spellCheckManager.changedLines[4]).to.equal(false)
expect(this.spellCheckManager.changedLines[5]).to.equal(false)
expect(this.spellCheckManager.changedLines[6]).to.equal(true)
})
it('ignores updated lines', function () {
this.spellCheckManager.changedLines[4] = false
this.spellCheckManager.runSpellCheck()
this.spellCheckManager.timeoutId = null
this.$httpBackend.flush()
expect(this.adapter.getLinesByRows).to.have.been.calledWith([3, 5])
})
it('clears highlights for changed lines', function () {
this.spellCheckManager.runSpellCheck()
this.spellCheckManager.timeoutId = null
this.$httpBackend.flush()
expect(
this.highlightedWordManager.clearRow.getCall(0).args[0]
).to.equal(3)
expect(
this.highlightedWordManager.clearRow.getCall(1).args[0]
).to.equal(4)
expect(
this.highlightedWordManager.clearRow.getCall(2).args[0]
).to.equal(5)
})
})
})
describe('cache', function () {
beforeEach(function () {
this.adapter.getLineCount.returns(1)
this.adapter.getFirstVisibleRowNum.returns(1)
this.adapter.getLastVisibleRowNum.returns(1)
this.adapter.getLinesByRows.returns(['Lorem ipsum dolor'])
this.$httpBackend.when('POST', '/spelling/check').respond({
misspellings: [
{
index: 0,
suggestions: ['foobarbaz'],
},
],
})
})
it('adds already checked words to the spellchecker cache', function () {
expect(this.spellCheckManager.cache.info().size).to.equal(0)
this.spellCheckManager.init()
this.timelord.tick(500)
this.$httpBackend.flush()
expect(this.spellCheckManager.cache.info().size).to.equal(3)
})
it('adds misspeled word suggestions to the cache', function () {
this.spellCheckManager.init()
this.timelord.tick(500)
this.$httpBackend.flush()
expect(
this.spellCheckManager.cache.get(
`${this.scope.spellCheckLanguage}:Lorem`
)
).to.eql(['foobarbaz'])
})
it('adds non-misspeled words to the cache as a boolean', function () {
this.spellCheckManager.init()
this.timelord.tick(500)
this.$httpBackend.flush()
expect(
this.spellCheckManager.cache.get(
`${this.scope.spellCheckLanguage}:ipsum`
)
).to.equal(true)
})
})
describe('backend', function () {
beforeEach(function () {
this.adapter.getLineCount.returns(1)
this.adapter.getFirstVisibleRowNum.returns(1)
this.adapter.getLastVisibleRowNum.returns(1)
this.adapter.getLinesByRows.returns([
'Lorem \\somecommand ipsum dolor \\othercommand',
])
})
it('hits the backend with all words at startup', function () {
this.$httpBackend
.expect('POST', '/spelling/check', {
language: this.scope.spellCheckLanguage,
words: ['Lorem', 'ipsum', 'dolor'],
skipLearnedWords: true,
token: window.user.id,
_csrf: window.csrfToken,
})
.respond({
misspellings: [
{
index: 0,
suggestions: ['foobarbaz'],
},
],
})
this.spellCheckManager.init()
this.timelord.tick(500)
this.$httpBackend.flush()
})
it('does not hit the backend when all words are already in the cache', function () {
this.$httpBackend
.expect('POST', '/spelling/check', {
language: this.scope.spellCheckLanguage,
words: ['Lorem', 'ipsum', 'dolor'],
skipLearnedWords: true,
token: window.user.id,
_csrf: window.csrfToken,
})
.respond({
misspellings: [
{
index: 0,
suggestions: ['foobarbaz'],
},
],
})
this.spellCheckManager.init()
this.timelord.tick(500)
this.$httpBackend.flush()
this.spellCheckManager.init()
this.timelord.tick(500)
})
it('hits the backend only with non-cached words', function () {
this.$httpBackend
.expect('POST', '/spelling/check', {
language: this.scope.spellCheckLanguage,
words: ['Lorem', 'ipsum', 'dolor'],
skipLearnedWords: true,
token: window.user.id,
_csrf: window.csrfToken,
})
.respond({
misspellings: [
{
index: 0,
suggestions: ['foobarbaz'],
},
],
})
this.spellCheckManager.init()
this.timelord.tick(500)
this.$httpBackend.flush()
this.adapter.getLinesByRows.returns(['Lorem ipsum dolor sit amet'])
this.$httpBackend
.expect('POST', '/spelling/check', {
language: this.scope.spellCheckLanguage,
words: ['sit', 'amet'],
skipLearnedWords: true,
token: window.user.id,
_csrf: window.csrfToken,
})
.respond({
misspellings: [
{
index: 0,
suggestions: ['bazbarfoo'],
},
],
})
this.spellCheckManager.init()
this.timelord.tick(500)
this.$httpBackend.flush()
})
afterEach(function () {
this.$httpBackend.verifyNoOutstandingRequest()
this.$httpBackend.verifyNoOutstandingExpectation()
})
})
})

View file

@ -1,649 +0,0 @@
/* eslint-disable
max-len,
no-return-assign,
*/
/* global inject, sinon */
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import HistoryV2Manager from '../../../../frontend/js/ide/history/HistoryV2Manager'
export default describe('HistoryV2Manager', function () {
beforeEach(function (done) {
this.defaultHistoryScope = {
updates: [],
viewMode: 'point_in_time',
nextBeforeTimestamp: null,
loading: false,
atEnd: false,
userHasFullFeature: undefined,
freeHistoryLimitHit: false,
selection: {
docs: {},
pathname: null,
range: {
fromV: null,
toV: null,
},
hoveredRange: {
fromV: null,
toV: null,
},
diff: null,
files: [],
file: null,
},
error: null,
showOnlyLabels: false,
labels: null,
loadingFileTree: true,
isReact: false,
}
this.sampleUpdates = [
{
fromV: 4,
toV: 5,
meta: {
users: [
{
first_name: 'john.doe',
last_name: '',
email: 'john.doe@domain.tld',
id: '5b57299087712202fb599ab4',
hue: 200,
},
],
start_ts: 1544021278346,
end_ts: 1544021278346,
},
labels: [
{
id: '5c07e822042e67003b448f18',
comment: 'My first label',
version: 5,
user_id: '5b57299087712202fb599ab4',
created_at: '2018-12-05T15:00:50.688Z',
},
],
pathnames: [],
project_ops: [
{
add: {
pathname: 'chapters/chapter1.tex',
},
atV: 4,
},
],
},
{
fromV: 3,
toV: 4,
meta: {
users: [
{
first_name: 'john.doe',
last_name: '',
email: 'john.doe@domain.tld',
id: '5b57299087712202fb599ab4',
hue: 200,
},
],
start_ts: 1544021262622,
end_ts: 1544021262622,
},
labels: [],
pathnames: ['main.tex'],
project_ops: [],
},
{
fromV: 0,
toV: 3,
meta: {
users: [
{
first_name: 'john.doe',
last_name: '',
email: 'john.doe@domain.tld',
id: '5b57299087712202fb599ab4',
hue: 200,
},
],
start_ts: 1544021213540,
end_ts: 1544021213618,
},
labels: [],
pathnames: [],
project_ops: [
{
add: {
pathname: 'universe.jpg',
},
atV: 2,
},
{
add: {
pathname: 'references.bib',
},
atV: 1,
},
{
add: {
pathname: 'main.tex',
},
atV: 0,
},
],
},
]
inject(($q, $http, $filter, $rootScope) => {
this.$scope = $rootScope.$new()
this.$scope.project = {
features: {
versioning: true,
},
}
this.$scope.user = {
isAdmin: false,
}
this.ide = {
globalEditorWatchdogManager: { attachToEditor() {} },
$q,
$http,
$filter,
}
this.eventTracking = {
sendMB: () => {},
}
this.localStorage = sinon.stub().returns(null)
this.historyManager = new HistoryV2Manager(
this.ide,
this.$scope,
this.localStorage,
this.eventTracking
)
done()
})
})
it('should setup the history scope on initialization', function () {
expect(this.$scope.history).to.deep.equal(this.defaultHistoryScope)
})
it('should keep history updates after performing a soft reset', function () {
const historyScopeWithUpdates = Object.assign(
{},
this.defaultHistoryScope,
{
updates: this.sampleUpdates,
}
)
this.$scope.history.updates = this.sampleUpdates
this.historyManager.softReset()
expect(this.$scope.history).to.deep.equal(historyScopeWithUpdates)
})
it('should discard history updates after performing a hard reset', function () {
this.$scope.history.updates = this.sampleUpdates
this.historyManager.hardReset()
expect(this.$scope.history).to.deep.equal(this.defaultHistoryScope)
})
it('should setup history with full access to the feature if the project has versioning', function () {
this.$scope.$digest()
expect(this.$scope.history.userHasFullFeature).to.equal(true)
})
it('should setup history without full access to the feature if the project does not have versioning', function () {
this.$scope.project.features.versioning = false
this.historyManager = new HistoryV2Manager(
this.ide,
this.$scope,
this.localStorage
)
this.$scope.$digest()
expect(this.$scope.history.userHasFullFeature).to.equal(false)
})
it('should setup history with full access to the feature for admin users even if the project does not have versioning', function () {
this.$scope.project.features.versioning = false
this.$scope.user.isAdmin = true
this.historyManager = new HistoryV2Manager(
this.ide,
this.$scope,
this.localStorage
)
this.$scope.$digest()
expect(this.$scope.history.userHasFullFeature).to.equal(true)
})
describe('autoSelectFile', function () {
describe('for compare mode', function () {
beforeEach(function () {
this.mockedFilesList = [
{
pathname: 'main.tex',
},
{
pathname: 'references.bib',
},
{
pathname: 'universe.jpg',
},
{
pathname: 'chapters/chapter2.tex',
},
{
pathname: 'chapters/draft.tex',
operation: 'removed',
deletedAtV: 47,
},
{
pathname: 'chapters/chapter3.tex',
operation: 'added',
},
{
pathname: 'chapters/chapter1.tex',
operation: 'edited',
},
{
pathname: 'chapters/foo.tex',
oldPathname: 'chapters/bar.tex',
operation: 'renamed',
},
]
this.mockedMainTex = this.mockedFilesList[0]
this.mockedReferencesFile = this.mockedFilesList[1]
this.mockedRemovedFile = this.mockedFilesList[4]
this.mockedAddedFile = this.mockedFilesList[5]
this.mockedEditedFile = this.mockedFilesList[6]
this.mockedRenamedFile = this.mockedFilesList[7]
this.$scope.history.viewMode = 'compare'
this.$scope.history.selection.files = this.mockedFilesList
})
describe('with a previously selected file', function () {
it('should prefer the previously selected file if it is available and has operations', function () {
this.historyManager._previouslySelectedPathname =
this.mockedAddedFile.pathname
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file).to.deep.equal(
this.mockedAddedFile
)
})
it('should prefer a file with ops if the previously selected file is available but has no operations', function () {
this.historyManager._previouslySelectedPathname =
this.mockedReferencesFile.pathname
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file.operation).to.exist
})
it('should ignore the previously selected file if it is not available', function () {
this.historyManager._previouslySelectedPathname = 'non/existent.file'
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file.pathname).to.not.equal(
'non/existent.file'
)
})
})
describe('without a previously selected file, with a list of files containing operations', function () {
it('should prefer edited files', function () {
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file).to.deep.equal(
this.mockedEditedFile
)
})
it('should prefer added files if no edited files are present', function () {
const indexOfEditedFile = this.$scope.history.selection.files.indexOf(
this.mockedEditedFile
)
this.$scope.history.selection.files.splice(indexOfEditedFile, 1)
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file).to.deep.equal(
this.mockedAddedFile
)
})
it('should prefer renamed files if no edited or added files are present', function () {
const indexOfEditedFile = this.$scope.history.selection.files.indexOf(
this.mockedEditedFile
)
this.$scope.history.selection.files.splice(indexOfEditedFile, 1)
const indexOfAddedFile = this.$scope.history.selection.files.indexOf(
this.mockedAddedFile
)
this.$scope.history.selection.files.splice(indexOfAddedFile, 1)
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file).to.deep.equal(
this.mockedRenamedFile
)
})
it('should prefer removed files if no edited, added or renamed files are present', function () {
const indexOfEditedFile = this.$scope.history.selection.files.indexOf(
this.mockedEditedFile
)
this.$scope.history.selection.files.splice(indexOfEditedFile, 1)
const indexOfAddedFile = this.$scope.history.selection.files.indexOf(
this.mockedAddedFile
)
this.$scope.history.selection.files.splice(indexOfAddedFile, 1)
const indexOfRenamedFile =
this.$scope.history.selection.files.indexOf(this.mockedRenamedFile)
this.$scope.history.selection.files.splice(indexOfRenamedFile, 1)
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file).to.deep.equal(
this.mockedRemovedFile
)
})
})
describe('without a previously selected file, with a list of files without operations', function () {
beforeEach(function () {
this.mockedFilesListWithNoOps = [
{
pathname: 'main.tex',
},
{
pathname: 'references.bib',
},
{
pathname: 'other.tex',
},
{
pathname: 'universe.jpg',
},
]
this.mockedMainTex = this.mockedFilesListWithNoOps[0]
this.mockedReferencesFile = this.mockedFilesListWithNoOps[1]
this.mockedOtherTexFile = this.mockedFilesListWithNoOps[2]
this.$scope.history.selection.files = this.mockedFilesListWithNoOps
})
it('should prefer main.tex if it exists', function () {
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file).to.deep.equal(
this.mockedMainTex
)
})
it('should prefer another tex file if main.tex does not exist', function () {
const indexOfMainTex = this.$scope.history.selection.files.indexOf(
this.mockedMainTex
)
this.$scope.history.selection.files.splice(indexOfMainTex, 1)
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file).to.deep.equal(
this.mockedOtherTexFile
)
})
it('should pick the first available file if no tex files are available', function () {
const indexOfMainTex = this.$scope.history.selection.files.indexOf(
this.mockedMainTex
)
this.$scope.history.selection.files.splice(indexOfMainTex, 1)
const indexOfOtherTexFile =
this.$scope.history.selection.files.indexOf(this.mockedOtherTexFile)
this.$scope.history.selection.files.splice(indexOfOtherTexFile, 1)
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file).to.deep.equal(
this.mockedReferencesFile
)
})
})
})
describe('for point-in-time mode', function () {
beforeEach(function () {
this.$scope.history.viewMode = 'point_in_time'
this.sampleUpdates[0] = {
fromV: 4,
toV: 5,
meta: {
users: [
{
first_name: 'john.doe',
last_name: '',
email: 'john.doe@domain.tld',
id: '5b57299087712202fb599ab4',
hue: 200,
},
],
start_ts: 1544021278346,
end_ts: 1544021278346,
},
pathnames: ['main.tex'],
project_ops: [
{
add: {
pathname: 'chapters/chapter1.tex',
},
atV: 4,
},
{
rename: {
pathname: 'foo.tex',
newPathname: 'bar.tex',
},
},
],
}
this.sampleUpdateEditedFile = this.sampleUpdates[0].pathnames[0]
this.sampleUpdateAddedFile =
this.sampleUpdates[0].project_ops[0].add.pathname
this.sampleUpdateRenamedFile =
this.sampleUpdates[0].project_ops[1].rename.newPathname
this.$scope.history.updates = this.sampleUpdates
this.$scope.history.selection.range = {
fromV: this.sampleUpdates[0].toV,
toV: this.sampleUpdates[0].toV,
}
this.mockedFilesList = [
{
pathname: 'main.tex',
},
{
pathname: 'references.bib',
},
{
pathname: 'universe.jpg',
},
{
pathname: 'chapters/chapter2.tex',
},
{
pathname: 'chapters/draft.tex',
},
{
pathname: 'chapters/chapter3.tex',
},
{
pathname: 'chapters/chapter1.tex',
},
{
pathname: 'bar.tex',
},
]
this.$scope.history.selection.files = this.mockedFilesList
})
describe('with a previously selected file', function () {
it('should prefer the previously selected file if it is available and has operations', function () {
this.historyManager._previouslySelectedPathname = 'main.tex'
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file.pathname).to.equal(
this.sampleUpdateEditedFile
)
})
it('should prefer a file with ops if the previously selected file is available but has no operations', function () {
this.historyManager._previouslySelectedPathname = 'main.tex'
this.sampleUpdates[0].pathnames = []
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file.pathname).to.equal(
this.sampleUpdateAddedFile
)
})
it('should ignore the previously selected file if it is not available', function () {
this.historyManager._previouslySelectedPathname = 'non/existent.file'
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file.pathname).to.not.equal(
'non/existent.file'
)
})
})
describe('without a previously selected file, with a list of files containing operations', function () {
it('should prefer edited files', function () {
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file.pathname).to.equal(
this.sampleUpdateEditedFile
)
})
it('should prefer added files if no edited files are present', function () {
this.sampleUpdates[0].pathnames = []
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file.pathname).to.equal(
this.sampleUpdateAddedFile
)
})
it('should prefer renamed files if no edited or added files are present', function () {
this.sampleUpdates[0].pathnames = []
this.sampleUpdates[0].project_ops.shift()
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file.pathname).to.equal(
this.sampleUpdateRenamedFile
)
})
})
describe('without a previously selected file, with a list of files without operations', function () {
beforeEach(function () {
this.sampleUpdates[0].pathnames = []
this.sampleUpdates[0].project_ops = []
this.mockedMainTex = this.mockedFilesList[0]
this.mockedReferencesFile = this.mockedFilesList[1]
})
it('should prefer main.tex if it exists', function () {
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file.pathname).to.equal(
this.mockedMainTex.pathname
)
})
it('should prefer another tex file if main.tex does not exist', function () {
const indexOfMainTex = this.$scope.history.selection.files.indexOf(
this.mockedMainTex
)
this.$scope.history.selection.files.splice(indexOfMainTex, 1)
this.historyManager.autoSelectFile()
expect(this.$scope.history.selection.file.pathname).to.match(/.tex$/)
})
})
})
describe('_loadLabels', function () {
it('should return labels list as is if there is a label for the last version', function () {
const labels = [
{
id: '1',
version: 1,
comment: 'foo',
created_at: new Date().toISOString(),
},
{
id: '2',
version: 2,
comment: 'bar',
created_at: new Date().toISOString(),
},
{
id: '3',
version: 3,
comment: 'baz',
created_at: new Date().toISOString(),
},
]
const lastUpdate = 3
const labelsResult = this.historyManager._loadLabels(labels, lastUpdate)
expect(labelsResult).to.have.members(labels)
})
it('should return a labels list with a pseudo current state label if there is no label for the last version', function () {
const labels = [
{
id: '1',
version: 1,
comment: 'foo',
created_at: new Date().toISOString(),
},
{
id: '2',
version: 2,
comment: 'bar',
created_at: new Date().toISOString(),
},
{
id: '3',
version: 3,
comment: 'baz',
created_at: new Date().toISOString(),
},
]
const lastUpdate = 5
const labelsResult = this.historyManager._loadLabels(labels, lastUpdate)
expect(labelsResult).to.include.members(labels)
expect(labelsResult[0].isPseudoCurrentStateLabel).to.equal(true)
expect(labelsResult[0].version).to.equal(5)
})
it('should keep pseudo label when deleting label', function () {
this.historyManager.$scope.history.labels = [
{
id: '1',
version: 1,
comment: 'foo',
created_at: new Date().toISOString(),
},
]
const lastUpdate = 5
this.historyManager.$scope.history.labels =
this.historyManager._loadLabels(
this.historyManager.$scope.history.labels,
lastUpdate
)
expect(
this.historyManager.$scope.history.labels[0].isPseudoCurrentStateLabel
).to.equal(true)
this.historyManager.$scope.history.labels =
this.historyManager._loadLabels([], lastUpdate)
expect(
this.historyManager.$scope.history.labels[0].isPseudoCurrentStateLabel
).to.equal(true)
expect(this.historyManager.$scope.history.labels[0].version).to.equal(5)
})
})
})
})

View file

@ -1,45 +0,0 @@
/* global chai */
// Allow for mocking of Angular
import 'angular'
import 'angular-mocks'
/**
* Add chai assertion for comparing CodeMirror Pos objects.
* A deep comparison will fail because CodeMirror inserts additional properties
* that we want to ignore.
*/
chai.Assertion.addMethod('equalPos', function (expectedPos) {
const { line: actualLine, ch: actualCh } = this._obj
const { line: expectedLine, ch: expectedCh } = expectedPos
this.assert(
actualLine === expectedLine && actualCh === expectedCh,
`expected #{exp} to equal #{act}`,
`expected #{exp} to not equal #{act}`,
`Pos({ line: ${expectedLine}, ch: ${expectedCh} })`,
`Pos({ line: ${actualLine}, ch: ${actualCh} })`
)
})
// Mock ExposedSettings
window.ExposedSettings = {}
/*
* Bundle all test files together into a single bundle, and run tests against
* this single bundle.
* We are using karma-webpack to bundle our tests and the 'default' strategy is
* to create a bundle for each test file. This isolates the tests better, but
* causes a problem with Angular. The issue with Angular tests is because we
* load a single global copy of Angular (see karma.conf.js) but
* frontend/js/base.js is included in each bundle, meaning the Angular app is
* initialised for each bundle when it is loaded onto the page when Karma
* starts. This means that only the last bundle will have controllers/directives
* registered against it, ultimately meaning that all other bundles will fail
* because Angular cannot find the controller/directive under test.
*/
// Import from the top-level any JS files within a test/karma
// directory
const context = require.context('../../', true, /test\/karma\/.*\.js$/)
context.keys().forEach(context)

View file

@ -1,18 +0,0 @@
class Fixture {
constructor() {
this.el = document.createElement('div')
document.body.appendChild(this.el)
}
load(html) {
this.el.innerHTML = html
return this.el.firstChild
}
cleanUp() {
this.el.innerHTML = ''
}
}
const fixture = new Fixture()
export default fixture

View file

@ -1,16 +0,0 @@
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { merge } = require('webpack-merge')
const base = require('./webpack.config')
const config = merge(base, {
mode: 'development',
plugins: [new MiniCssExtractPlugin()],
})
// Karma configures entry & output for us, so disable these
delete config.entry
delete config.output
module.exports = config