Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enh(tests): Add unit tests and CI workflow for testing #564

Merged
merged 1 commit into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/workflows/node-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Node tests

on:
pull_request:
push:
branches:
- main
- master
- stable*

permissions:
contents: read

concurrency:
group: node-tests-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
changes:
runs-on: ubuntu-latest

outputs:
src: ${{ steps.changes.outputs.src}}

steps:
- uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1
id: changes
continue-on-error: true
with:
filters: |
src:
- '.github/workflows/**'
- '__tests__/**'
- '__mocks__/**'
- 'src/**'
- 'appinfo/info.xml'
- 'package.json'
- 'package-lock.json'
- 'tsconfig.json'
- '**.js'
- '**.ts'
- '**.vue'

test:
runs-on: ubuntu-latest

needs: changes
if: needs.changes.outputs.src != 'false'

steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

- name: Read package.json node and npm engines version
uses: skjnldsv/read-package-engines-version-actions@8205673bab74a63eb9b8093402fd9e0e018663a1 # v2.2
id: versions
with:
fallbackNode: '^20'
fallbackNpm: '^9'

- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: ${{ steps.versions.outputs.nodeVersion }}

- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"

- name: Install dependencies & build
env:
CYPRESS_INSTALL_BINARY: 0
run: |
npm ci
npm run build --if-present

- name: Test
run: npm run test --if-present

- name: Test and process coverage
run: npm run test:coverage --if-present

- name: Collect coverage
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4
with:
files: ./coverage/lcov.info

summary:
permissions:
contents: none
runs-on: ubuntu-latest
needs: [changes, test]

if: always()

name: test-summary

steps:
- name: Summary status
run: if ${{ needs.changes.outputs.src != 'false' && needs.test.result != 'success' }}; then exit 1; fi
132 changes: 132 additions & 0 deletions __tests__/paths.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* @copyright 2024 Ferdinand Thiessen <opensource@fthiessen.de
*
* @author Ferdinand Thiessen <opensource@fthiessen.de
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

import { beforeAll, beforeEach, describe, expect, test } from 'vitest'
import { generateFilePath, imagePath, linkTo } from '../lib/index'

declare global {
interface Window {
_oc_appswebroots?: Record<string, string|undefined>
_oc_webroot?: string
OC?: Record<string, unknown>
}
}

describe('Path generation', () => {
beforeAll(() => {
window.OC = {
coreApps: ['', 'admin', 'log', 'core/search', 'core', '3rdparty'],
}
})

describe('generateFilePath', () => {
beforeEach(() => {
window._oc_webroot = ''
window._oc_appswebroots = { forms: '/apps-extra/forms' }
})

test('non core PHP index file', () => {
expect(generateFilePath('forms', '', 'index.php')).toBe('/index.php/apps/forms')
})

// TODO: This feels wrong, I would expect `/index.php/apps/forms/templates`
test('non core PHP index files with type', () => {
expect(generateFilePath('forms', 'templates', 'index.php')).toBe('/index.php/apps/forms')
})

test('non core PHP file', () => {
expect(generateFilePath('forms', '', 'version.php')).toBe('/index.php/apps/forms/version.php')
})

test('non core PHP file with type', () => {
expect(generateFilePath('forms', 'templates', 'version.php')).toBe('/index.php/apps/forms/templates/version.php')
})

test('non core file', () => {
expect(generateFilePath('forms', '', 'file.js')).toBe('/apps-extra/forms/file.js')
})

test('non core file with type', () => {
expect(generateFilePath('forms', 'js', 'file.js')).toBe('/apps-extra/forms/js/file.js')
})

test('core PHP file with ajax type', () => {
expect(generateFilePath('admin', 'ajax', 'file.php')).toBe('/admin/ajax/file.php')
})

test('special core PHP file with ajax type', () => {
expect(generateFilePath('core', 'ajax', 'file.php')).toBe('/index.php/core/ajax/file.php')
})

test('empty app file path', () => {
expect(generateFilePath('', '', 'file.php')).toBe('/file.php')
})

test('empty app file path with type', () => {
expect(generateFilePath('', 'ajax', 'file.php')).toBe('/ajax/file.php')
})
})

describe('linkTo', () => {
test('non core PHP index file', () => {
expect(linkTo('forms', 'index.php')).toBe('/index.php/apps/forms')
})

test('non core PHP file', () => {
expect(linkTo('forms', 'version.php')).toBe('/index.php/apps/forms/version.php')
})

test('non core file', () => {
expect(linkTo('forms', 'file.js')).toBe('/apps-extra/forms/file.js')
})

test('empty app file path', () => {
expect(linkTo('', 'file.php')).toBe('/file.php')
})
})

describe('imagePath', () => {
test('non core file without extension', () => {
expect(imagePath('forms', 'color')).toBe('/apps-extra/forms/img/color.svg')
})

test('non core file with extension', () => {
expect(imagePath('forms', 'color.png')).toBe('/apps-extra/forms/img/color.png')
})

test('core file without extension', () => {
expect(imagePath('admin', 'color')).toBe('/admin/img/color.svg')
})

test('core file with extension', () => {
expect(imagePath('admin', 'color.png')).toBe('/admin/img/color.png')
})

test('empty app without extension', () => {
expect(imagePath('', 'color')).toBe('/img/color.svg')
})

test('empty app without extension', () => {
expect(imagePath('', 'color.png')).toBe('/img/color.png')
})
})
})
7 changes: 7 additions & 0 deletions __tests__/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"include": ["./**.ts"],
"extends": "../tsconfig.json",
"compilerOptions": {
"rootDir": ".."
}
}
126 changes: 126 additions & 0 deletions __tests__/urls.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* @copyright 2024 Ferdinand Thiessen <opensource@fthiessen.de
*
* @author Ferdinand Thiessen <opensource@fthiessen.de
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

import { beforeAll, beforeEach, describe, expect, it, test } from 'vitest'
import { generateOcsUrl, generateRemoteUrl, generateUrl } from '../lib/index'

declare global {
interface Window {
_oc_appswebroots?: Record<string, string|undefined>
_oc_webroot?: string
OC?: Record<string, unknown>
}
}

describe('URL generation', () => {
beforeAll(() => {
window.OC = {
coreApps: ['', 'admin', 'log', 'core/search', 'core', '3rdparty'],
}
})

test('generateRemoteUrl', () => {
window._oc_webroot = '/nextcloud'
expect(generateRemoteUrl('dav')).toBe(`${window.location.href}nextcloud/remote.php/dav`)
})

describe('generateOcsUrl', () => {
beforeEach(() => {
window._oc_webroot = ''
})

it('uses OCSv2 by default', () => {
expect(generateOcsUrl('/foo/bar')).toBe(`${window.location.href}ocs/v2.php/foo/bar`)
})

it('can use OCSv1', () => {
expect(generateOcsUrl('/foo/bar', undefined, { ocsVersion: 1 })).toBe(`${window.location.href}ocs/v1.php/foo/bar`)
})

it('starts with webroot', () => {
window._oc_webroot = '/nextcloud'
expect(generateOcsUrl('/foo/bar')).toBe(`${window.location.href}nextcloud/ocs/v2.php/foo/bar`)
})

it('replaces parameters', () => {
expect(generateOcsUrl('/foo/{bar}', { bar: 'hello' })).toBe(`${window.location.href}ocs/v2.php/foo/hello`)
})
})

describe('generateUrl', () => {
beforeEach(() => {
window.OC.config = { modRewriteWorking: false }
window._oc_webroot = ''
})

it('starts with webroot', () => {
window._oc_webroot = '/nextcloud'
expect(generateUrl('/foo/bar')).toBe('/nextcloud/index.php/foo/bar')
})

it('works without webroot', () => {
(window.OC.config as Record<string, unknown>).modRewriteWorking = true
// meaning it injects '/' at the beginning
expect(generateUrl('foo')).toBe('/foo')
})

it('respects disabled mod-rewrite', () => {
expect(generateUrl('/foo/bar')).toMatch(/index\.php/)
})

it('respects mod-rewrite', () => {
(window.OC.config as Record<string, unknown>).modRewriteWorking = true

expect(generateUrl('/foo/bar')).not.toMatch(/index\.php/)
})

it('force disable mod-rewrite', () => {
(window.OC.config as Record<string, unknown>).modRewriteWorking = true

expect(generateUrl('/foo/bar', undefined, { noRewrite: true })).toMatch(/index\.php/)
})

it('replaces string parameters', () => {
expect(generateUrl('/foo/{bar}', { bar: 'hello' })).toBe('/index.php/foo/hello')
})

it('replaces numeric parameters', () => {
expect(generateUrl('/foo/{bar}', { bar: 123 })).toBe('/index.php/foo/123')
})

it('escapes parameters', () => {
expect(generateUrl('/foo/{bar}', { bar: 'hello world' })).toBe('/index.php/foo/hello%20world')
})

it('can disabled escaping of parameters', () => {
expect(generateUrl('/foo/{bar}', { bar: 'hello world' }, { escape: false })).toBe('/index.php/foo/hello world')
})

it('does not replace invalid parameters', () => {
expect(generateUrl('/foo/{bar}', { bar: true })).toBe('/index.php/foo/%7Bbar%7D')
})

it('does not replace invalid parameters and keeps curly brackets when unescaped', () => {
expect(generateUrl('/foo/{bar}', { bar: true }, { escape: false })).toBe('/index.php/foo/{bar}')
})
})
})