Merge branch 'tests' of https://github.com/jankapunkt/wekan into jankapunkt-tests

This commit is contained in:
Lauri Ojansivu 2021-06-23 13:29:51 +03:00
commit 66729e4c05
13 changed files with 1155 additions and 5975 deletions

View file

@ -1,5 +1,12 @@
{
"presets": [
"@babel/preset-stage-3"
]
],
"env": {
"COVERAGE": {
"plugins": [
"istanbul"
]
}
}
}

161
.github/workflows/test_suite.yml vendored Normal file
View file

@ -0,0 +1,161 @@
name: Test suite
on:
push:
branches:
- master
- develop
pull_request:
jobs:
# the following are optional jobs and need to be configured according
# to this project's settings:
#
# lintcode:
# name: Javascript lint
# runs-on: ubuntu-latest
# steps:
# - name: checkout
# uses: actions/checkout@v2
#
# - name: setup node
# uses: actions/setup-node@v1
# with:
# node-version: '12.x'
#
# - name: cache dependencies
# uses: actions/cache@v1
# with:
# path: ~/.npm
# key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# restore-keys: |
# ${{ runner.os }}-node-
#
# - run: npm install
# - run: npm run lint:code
#
# lintstyle:
# name: SCSS lint
# runs-on: ubuntu-latest
# needs: [lintcode]
# steps:
# - name: checkout
# uses: actions/checkout@v2
#
# - name: setup node
# uses: actions/setup-node@v1
# with:
# node-version: '12.x'
#
# - name: cache dependencies
# uses: actions/cache@v1
# with:
# path: ~/.npm
# key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# restore-keys: |
# ${{ runner.os }}-node-
# - run: npm install
# - run: npm run lint:style
#
# lintdocs:
# name: documentation lint
# runs-on: ubuntu-latest
# needs: [lintcode,lintstyle]
# steps:
# - name: checkout
# uses: actions/checkout@v2
#
# - name: setup node
# uses: actions/setup-node@v1
# with:
# node-version: '12.x'
#
# - name: cache dependencies
# uses: actions/cache@v1
# with:
# path: ~/.npm
# key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# restore-keys: |
# ${{ runner.os }}-node-
#
# - run: npm install
# - run: npm run lint:markdown
tests:
name: Meteor ${{ matrix.meteor }} tests
runs-on: ubuntu-latest
steps:
# CHECKOUTS
- name: Checkout
uses: actions/checkout@v2
# CACHING
- name: Install Meteor
id: cache-meteor-install
uses: actions/cache@v2
with:
path: ~/.meteor
key: v1-meteor-${{ hashFiles('.meteor/versions') }}
restore-keys: |
v1-meteor-
- name: Cache NPM dependencies
id: cache-meteor-npm
uses: actions/cache@v2
with:
path: ~/.npm
key: v1-npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
v1-npm-
- name: Cache Meteor build
id: cache-meteor-build
uses: actions/cache@v2
with:
path: |
.meteor/local/resolver-result-cache.json
.meteor/local/plugin-cache
.meteor/local/isopacks
.meteor/local/bundler-cache/scanner
key: v1-meteor_build_cache-${{ github.ref }}-${{ github.sha }}
restore-key: |
v1-meteor_build_cache-
- name: Setup meteor
uses: meteorengineer/setup-meteor@v1
with:
meteor-release: '2.2'
- name: Install NPM Dependencies
run: meteor npm ci
- name: Run Tests
run: sh ./test-wekan.sh -cv
- name: Upload coverage
uses: actions/upload-artifact@v2
with:
name: coverage-folder
path: .coverage/
coverage:
name: Coverage report
runs-on: ubuntu-latest
needs: [tests]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Download coverage
uses: actions/download-artifact@v2
with:
name: coverage-folder
path: .coverage/
- name: Coverage Report
uses: VeryGoodOpenSource/very_good_coverage@v1.1.1
with:
path: ".coverage/lcov.info"
min_coverage: 1 # TODO add tests and increase to 95!

1
.gitignore vendored
View file

@ -33,3 +33,4 @@ ehthumbs.db
.meteor/local
.devcontainer/docker-compose.extend.yml
.devcontainer/volumes*/
.coverage

View file

@ -145,3 +145,5 @@ spacebars
easylogic:summernote
pascoual:pdfkit
wekan-accounts-lockout
lmieulet:meteor-coverage
meteortesting:mocha

View file

@ -69,6 +69,7 @@ lamhieu:meteorx@2.1.1
lamhieu:unblock@1.0.0
launch-screen@1.2.1
livedata@1.0.18
lmieulet:meteor-coverage@3.2.0
localstorage@1.2.0
logging@1.2.0
matb33:collection-hooks@0.9.1
@ -82,6 +83,9 @@ meteorhacks:collection-utils@1.2.0
meteorhacks:picker@1.0.3
meteorhacks:subs-manager@1.6.4
meteorspark:util@0.2.0
meteortesting:browser-tests@1.3.4
meteortesting:mocha@2.0.1
meteortesting:mocha-core@8.0.1
minifier-css@1.5.4
minifier-js@2.6.0
minifiers@1.1.8-faster-rebuild.0

View file

@ -0,0 +1,197 @@
/* eslint-env mocha */
import sinon from 'sinon';
import { expect } from 'chai';
import { Random } from 'meteor/random';
import '../utils';
describe('Utils', function() {
beforeEach(function() {
sinon.stub(Utils, 'reload').callsFake(() => {});
});
afterEach(function() {
window.localStorage.removeItem(boardView);
sinon.restore();
});
const boardView = 'boardView';
describe(Utils.setBoardView.name, function() {
it('sets the board view if the user exists', function(done) {
const viewId = Random.id();
const user = {
setBoardView: (view) => {
expect(view).to.equal(viewId);
done();
},
};
sinon.stub(Meteor, 'user').callsFake(() => user);
Utils.setBoardView(viewId);
expect(window.localStorage.getItem(boardView)).to.equal(viewId);
});
it('sets a specific view if no user exists but a view is defined', function() {
const views = [
'board-view-swimlanes',
'board-view-lists',
'board-view-cal'
];
sinon.stub(Meteor, 'user').callsFake(() => {});
views.forEach(viewName => {
Utils.setBoardView(viewName);
expect(window.localStorage.getItem(boardView)).to.equal(viewName);
});
});
it('sets a default view if no user and no view are given', function() {
sinon.stub(Meteor, 'user').callsFake(() => {});
Utils.setBoardView();
expect(window.localStorage.getItem(boardView)).to.equal('board-view-swimlanes');
});
});
describe(Utils.unsetBoardView.name, function() {
it('removes the boardview from localStoage', function() {
window.localStorage.setItem(boardView, Random.id());
window.localStorage.setItem('collapseSwimlane', Random.id());
Utils.unsetBoardView();
expect(window.localStorage.getItem(boardView)).to.equal(null);
expect(window.localStorage.getItem('collapseSwimlane')).to.equal(null);
});
});
describe(Utils.boardView.name, function() {
it('returns the user\'s board view if a user exists', function() {
const viewId = Random.id();
const user = {};
sinon.stub(Meteor, 'user').callsFake(() => user);
expect(Utils.boardView()).to.equal(undefined);
const boardView = Random.id();
user.profile = { boardView };
expect(Utils.boardView()).to.equal(boardView);
});
it('returns the current defined view', function() {
const views = [
'board-view-swimlanes',
'board-view-lists',
'board-view-cal'
];
sinon.stub(Meteor, 'user').callsFake(() => {});
views.forEach(viewName => {
window.localStorage.setItem(boardView, viewName);
expect(Utils.boardView()).to.equal(viewName);
});
});
it('returns a default if nothing is set', function() {
sinon.stub(Meteor, 'user').callsFake(() => {});
expect(Utils.boardView()).to.equal('board-view-swimlanes');
expect(window.localStorage.getItem(boardView)).to.equal('board-view-swimlanes');
});
});
describe(Utils.myCardsSort.name, function() {
it('has no tests yet');
});
describe(Utils.myCardsSortToggle.name, function() {
it('has no tests yet');
});
describe(Utils.setMyCardsSort.name, function() {
it('has no tests yet');
});
describe(Utils.archivedBoardIds.name, function() {
it('has no tests yet');
});
describe(Utils.dueCardsView.name, function() {
it('has no tests yet');
});
describe(Utils.setDueCardsView.name, function() {
it('has no tests yet');
});
describe(Utils.goBoardId.name, function() {
it('has no tests yet');
});
describe(Utils.goCardId.name, function() {
it('has no tests yet');
});
describe(Utils.processUploadedAttachment.name, function() {
it('has no tests yet');
});
describe(Utils.shrinkImage.name, function() {
it('has no tests yet');
});
describe(Utils.capitalize.name, function() {
it('has no tests yet');
});
describe(Utils.isMiniScreen.name, function() {
it('has no tests yet');
});
describe(Utils.isShowDesktopDragHandles.name, function() {
it('has no tests yet');
});
describe(Utils.isMiniScreenOrShowDesktopDragHandles.name, function() {
it('has no tests yet');
});
describe(Utils.calculateIndexData.name, function() {
it('has no tests yet');
});
describe(Utils.calculateIndex.name, function() {
it('has no tests yet');
});
describe(Utils.isTouchDevice.name, function() {
it('has no tests yet');
});
describe(Utils.calculateTouchDistance.name, function() {
it('has no tests yet');
});
describe(Utils.enableClickOnTouch.name, function() {
it('has no tests yet');
});
describe(Utils.manageCustomUI.name, function() {
it('has no tests yet');
});
describe(Utils.setCustomUI.name, function() {
it('has no tests yet');
});
describe(Utils.setMatomo.name, function() {
it('has no tests yet');
});
describe(Utils.manageMatomo.name, function() {
it('has no tests yet');
});
describe(Utils.getTriggerActionDesc.name, function() {
it('has no tests yet');
});
});

View file

@ -0,0 +1 @@
import './Utils.tests';

View file

@ -1,20 +1,27 @@
Utils = {
reload () {
// we move all window.location.reload calls into this function
// so we can disable it when running tests.
// This is because we are not allowed to override location.reload but
// we can override Utils.reload to prevent reload during tests.
window.location.reload();
},
setBoardView(view) {
currentUser = Meteor.user();
if (currentUser) {
Meteor.user().setBoardView(view);
} else if (view === 'board-view-swimlanes') {
window.localStorage.setItem('boardView', 'board-view-swimlanes'); //true
location.reload();
Utils.reload();
} else if (view === 'board-view-lists') {
window.localStorage.setItem('boardView', 'board-view-lists'); //true
location.reload();
Utils.reload();
} else if (view === 'board-view-cal') {
window.localStorage.setItem('boardView', 'board-view-cal'); //true
location.reload();
Utils.reload();
} else {
window.localStorage.setItem('boardView', 'board-view-swimlanes'); //true
location.reload();
Utils.reload();
}
},
@ -39,7 +46,7 @@ Utils = {
return 'board-view-cal';
} else {
window.localStorage.setItem('boardView', 'board-view-swimlanes'); //true
location.reload();
Utils.reload();
return 'board-view-swimlanes';
}
},
@ -64,7 +71,7 @@ Utils = {
setMyCardsSort(sort) {
window.localStorage.setItem('myCardsSort', sort);
location.reload();
Utils.reload();
},
archivedBoardIds() {
@ -87,7 +94,7 @@ Utils = {
setDueCardsView(view) {
window.localStorage.setItem('dueCardsView', view);
location.reload();
Utils.reload();
},
// XXX We should remove these two methods

6491
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,11 @@
},
"homepage": "https://wekan.github.io",
"devDependencies": {
"flatted": "^3.1.1"
"babel-plugin-istanbul": "^6.0.0",
"chai": "^4.3.4",
"flatted": "^3.1.1",
"puppeteer": "^10.0.0",
"sinon": "^11.1.1"
},
"dependencies": {
"@babel/core": "^7.14.0",
@ -41,5 +45,8 @@
"papaparse": "^5.3.0",
"qs": "^6.10.1",
"source-map-support": "^0.5.19"
},
"meteor": {
"testModule": "tests/main.js"
}
}

106
server/lib/utils.tests.js Normal file
View file

@ -0,0 +1,106 @@
/* eslint-env mocha */
import { Random } from 'meteor/random';
import { expect } from 'chai';
import './utils';
describe('utils', function() {
describe(allowIsBoardAdmin.name, function() {
it('returns if a board has an admin', function() {
const userId = Random.id();
const board = {
hasAdmin: id => {
return id === userId;
}
};
expect(allowIsBoardAdmin(userId, board)).to.equal(true);
expect(allowIsBoardAdmin(Random.id(), board)).to.equal(false);
});
});
describe(allowIsBoardMember.name, function() {
it('returns if a board has a member', function() {
const userId = Random.id();
const board = {
hasMember: id => {
return id === userId;
}
};
expect(allowIsBoardMember(userId, board)).to.equal(true);
expect(allowIsBoardMember(Random.id(), board)).to.equal(false);
});
});
describe(allowIsAnyBoardMember.name, function() {
it('returns if any board has a member', function() {
const userId = Random.id();
const boardsExpectedTrue = [{
hasMember: id => {
return id === userId;
}
}];
expect(allowIsAnyBoardMember(userId, boardsExpectedTrue)).to.equal(true);
expect(allowIsAnyBoardMember(Random.id(), boardsExpectedTrue)).to.equal(false);
const boardsExpectedFalse = [{
hasMember: () => false
}];
expect(allowIsAnyBoardMember(userId, boardsExpectedFalse)).to.equal(false);
expect(allowIsAnyBoardMember(Random.id(), boardsExpectedFalse)).to.equal(false);
});
});
describe(allowIsBoardMemberCommentOnly.name, function() {
it('returns if a board has a member that is not comment-only member', function() {
const userId = Random.id();
const board = {
hasMember: id => {
return id === userId;
},
hasCommentOnly: id => {
return id !== userId;
}
};
expect(allowIsBoardMemberCommentOnly(userId, board)).to.equal(true);
expect(allowIsBoardMemberCommentOnly(Random.id(), board)).to.equal(false);
});
});
describe(allowIsBoardMemberNoComments.name, function() {
it('returns if a board has a member that has comment any comments', function() {
const userId = Random.id();
const board = {
hasMember: id => {
return id === userId;
},
hasNoComments: id => {
return id !== userId;
}
};
expect(allowIsBoardMemberNoComments(userId, board)).to.equal(true);
expect(allowIsBoardMemberNoComments(Random.id(), board)).to.equal(false);
});
});
describe(allowIsBoardMemberByCard.name, function() {
it('returns if the board for a given card has a member', function() {
const userId = Random.id();
const board = {
hasMember: id => {
return id === userId;
}
};
const card = {
board: () => board
};
expect(allowIsBoardMemberByCard(userId, card)).to.equal(true);
expect(allowIsBoardMemberByCard(Random.id(), card)).to.equal(false);
});
});
});

98
test-wekan.sh Executable file
View file

@ -0,0 +1,98 @@
#!/usr/bin/env bash
set -e
# ------------------------------------------
#
# Variable declarations
#
# ------------------------------------------
PROJECT_ROOT=$(pwd)
PORT=4040
RUN_ONCE='--once'
VERBOSE_MODE=0
WATCH_MODE=0
COVERAGE=0
# ------------------------------------------
#
# Read args from script call
#
# ------------------------------------------
while getopts "vcw" opt; do
case $opt in
v)
VERBOSE_MODE=1
;;
c)
COVERAGE=1
;;
w)
WATCH_MODE=1
RUN_ONCE=''
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
esac
done
# ------------------------------------------
#
# Print variables on verbose mode
#
# ------------------------------------------
if [ "$VERBOSE_MODE" -eq "1" ];
then
PROJECT_NAME=`basename "$PROJECT_ROOT"`
echo "=> Test $PROJECT_NAME"
echo "=> Path: [${PROJECT_ROOT}]"
echo "=> Port: [${PORT}]"
echo "=> Watch mode: [${WATCH_MODE}] ${RUN_ONCE}"
echo "=> COVERAGE: [${COVERAGE}]"
fi
if [ "$WATCH_MODE" -eq "0" ];
then
# ---------------------------------------------------------------
# in cli mode we use a headless browser to include client tests
# and we activate the coverage reporting functionality
# ---------------------------------------------------------------
BABEL_ENV=COVERAGE \
TEST_BROWSER_DRIVER=puppeteer \
TEST_SERVER=1 \
TEST_CLIENT=1 \
COVERAGE=${COVERAGE} \
COVERAGE_OUT_HTML=1 \
COVERAGE_OUT_LCOVONLY=1 \
COVERAGE_OUT_TEXT_SUMMARY=1 \
COVERAGE_OUT_JSON_SUMMARY=1 \
COVERAGE_APP_FOLDER=$PWD/ \
COVERAGE_VERBOSE_MODE=${VERBOSE_MODE} \
meteor test \
--exclude-archs=web.browser.legacy,web.cordova \
--driver-package=meteortesting:mocha \
--settings=settings.json \
--port=${PORT} \
--once
cat ./.coverage/summary.txt
else
# ---------------------------------------------------------------
# in watch mode we neither use a browser driver, nor coverage
# se we speed up the test reload in the development phase
# ---------------------------------------------------------------
TEST_BROWSER_DRIVER=puppeteer \
TEST_SERVER=1 \
TEST_CLIENT=1 \
meteor test \
--exclude-archs=web.browser.legacy,web.cordova \
--driver-package=meteortesting:mocha \
--settings=settings.json \
--port=${PORT}
fi

30
tests/main.js Normal file
View file

@ -0,0 +1,30 @@
/* eslint-env mocha */
// This is the main test file from which all tests can be imported top-down,
// creating a directed sequence for tests that sums up to our test-suite.
//
// You propably want to start with low-level code and follow up to higher-level
// code, like for example:
//
// infrastructure
// utils / helpers
// contexts
// api
// components
// ui
// If you want to run tests on both, server AND client, simply import them as
// they are. However, if you want to restict tests to server-only or client-only
// you need to wrap them inside a new describe-block
if (Meteor.isServer) {
describe('server', function() {
import '../server/lib/utils.tests';
});
}
if (Meteor.isClient) {
describe('lib', function() {
import '../client/lib/tests';
});
}