mirror of https://github.com/nextcloud/server.git
Extract colour from custom background
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>pull/34696/head
parent
cedae7c6d7
commit
064fa10ecf
@ -0,0 +1,98 @@
|
||||
name: Cypress
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- stable*
|
||||
|
||||
env:
|
||||
APP_NAME: viewer
|
||||
BRANCH: ${{ github.base_ref }}
|
||||
TESTING: true
|
||||
|
||||
jobs:
|
||||
init:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Read package.json node and npm engines version
|
||||
uses: skjnldsv/read-package-engines-version-actions@v1.2
|
||||
id: versions
|
||||
with:
|
||||
fallbackNode: "^12"
|
||||
fallbackNpm: "^6"
|
||||
|
||||
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'npm'
|
||||
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 app
|
||||
run: |
|
||||
npm ci
|
||||
TESTING=true npm run build --if-present
|
||||
|
||||
- name: Save context
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: cypress-context-${{ github.run_id }}
|
||||
path: /home/runner/work/server
|
||||
|
||||
cypress:
|
||||
runs-on: ubuntu-latest
|
||||
needs: init
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# run multiple copies of the current job in parallel
|
||||
containers: [1]
|
||||
|
||||
name: runner ${{ matrix.containers }}
|
||||
|
||||
steps:
|
||||
- name: Restore context
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: cypress-context-${{ github.run_id }}
|
||||
path: /home/runner/work/server
|
||||
|
||||
- name: Run E2E cypress tests
|
||||
uses: cypress-io/github-action@v4
|
||||
with:
|
||||
record: true
|
||||
parallel: true
|
||||
# cypress env
|
||||
ci-build-id: ${{ github.sha }}-${{ github.run_number }}
|
||||
tag: ${{ github.event_name }}
|
||||
env:
|
||||
# Needs to be prefixed with CYPRESS_
|
||||
CYPRESS_BRANCH: ${{ env.BRANCH }}
|
||||
CYPRESS_GH: true
|
||||
# https://github.com/cypress-io/github-action/issues/124
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
|
||||
# Needed for some specific code workarounds
|
||||
TESTING: true
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
|
||||
summary:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [init, cypress]
|
||||
|
||||
if: always()
|
||||
|
||||
name: cypress-summary
|
||||
|
||||
steps:
|
||||
- name: Summary status
|
||||
run: if ${{ needs.init.result != 'success' || ( needs.cypress.result != 'success' && needs.cypress.result != 'skipped' ) }}; then exit 1; fi
|
@ -1,148 +0,0 @@
|
||||
#theming input {
|
||||
width: 230px;
|
||||
}
|
||||
#theming input:focus,
|
||||
#theming input:active {
|
||||
padding-right: 30px;
|
||||
}
|
||||
#theming .fileupload {
|
||||
display: none;
|
||||
}
|
||||
#theming div > label {
|
||||
position: relative;
|
||||
}
|
||||
#theming .theme-undo {
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
right: 4px;
|
||||
cursor: pointer;
|
||||
opacity: 0.3;
|
||||
padding: 7px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
#theming form.uploadButton {
|
||||
width: 411px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
#theming form .theme-undo,
|
||||
#theming .theme-remove-bg {
|
||||
cursor: pointer;
|
||||
opacity: 0.3;
|
||||
padding: 7px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
right: 0px;
|
||||
visibility: visible;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
margin-left: auto;
|
||||
}
|
||||
#theming form .theme-undo:not([style*="display:"]) ~ .theme-remove-bg {
|
||||
margin-left: 0;
|
||||
}
|
||||
#theming input[type=text]:hover + .theme-undo,
|
||||
#theming input[type=text] + .theme-undo:hover,
|
||||
#theming input[type=text]:focus + .theme-undo,
|
||||
#theming input[type=text]:active + .theme-undo,
|
||||
#theming input[type=url]:hover + .theme-undo,
|
||||
#theming input[type=url] + .theme-undo:hover,
|
||||
#theming input[type=url]:focus + .theme-undo,
|
||||
#theming input[type=url]:active + .theme-undo {
|
||||
visibility: visible;
|
||||
}
|
||||
#theming label span {
|
||||
display: inline-block;
|
||||
min-width: 175px;
|
||||
max-width: 175px;
|
||||
white-space: wrap;
|
||||
padding: 8px 0px;
|
||||
vertical-align: top;
|
||||
}
|
||||
#theming .icon-upload,
|
||||
#theming .uploadButton .icon-loading-small {
|
||||
padding: 8px 20px;
|
||||
width: 20px;
|
||||
margin: 2px 0px;
|
||||
min-height: 32px;
|
||||
display: inline-block;
|
||||
}
|
||||
#theming #theming_settings_status {
|
||||
height: 26px;
|
||||
margin: 10px;
|
||||
}
|
||||
#theming #theming_settings_loading {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
#theming #theming_settings_msg {
|
||||
vertical-align: middle;
|
||||
border-radius: 3px;
|
||||
}
|
||||
#theming #theming-preview {
|
||||
width: 230px;
|
||||
height: 140px;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
text-align: center;
|
||||
margin-left: 178px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
cursor: pointer;
|
||||
background-color: var(--color-primary-default);
|
||||
background-image: var(--image-background-default, var(--image-background-plain, linear-gradient(40deg, #0082c9 0%, #30b6ff 100%)));
|
||||
}
|
||||
#theming #theming-preview #theming-preview-logo {
|
||||
cursor: pointer;
|
||||
width: 20%;
|
||||
height: 20%;
|
||||
margin-top: 20px;
|
||||
display: inline-block;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
background-image: var(--image-logo, url("../../../core/img/logo/logo.svg"));
|
||||
}
|
||||
#theming .theming-hints {
|
||||
margin-top: 20px;
|
||||
}
|
||||
#theming .image-preview {
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
height: 36px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
#theming #theming-preview-logoheader {
|
||||
background-image: var(--image-logoheader);
|
||||
}
|
||||
#theming #theming-preview-favicon {
|
||||
background-image: var(--image-favicon);
|
||||
}
|
||||
#theming #user-theming {
|
||||
margin-top: 44px;
|
||||
display: flex;
|
||||
}
|
||||
#theming #user-theming > div {
|
||||
max-width: 400px;
|
||||
margin-bottom: 44px;
|
||||
}
|
||||
|
||||
/* transition effects for theming value changes */
|
||||
#header {
|
||||
transition: background-color 500ms linear;
|
||||
}
|
||||
#header svg, #header img {
|
||||
transition: 500ms filter linear;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=settings-admin.css.map */
|
@ -1,168 +0,0 @@
|
||||
#theming {
|
||||
input {
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
input:active {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.fileupload {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div > label {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.theme-undo {
|
||||
position: absolute;
|
||||
top: -7px; // input padding
|
||||
right: 4px; // input right margin + border
|
||||
cursor: pointer;
|
||||
opacity: .3;
|
||||
padding: 7px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
height: 32px; // height of input
|
||||
width: 32px; // height of input
|
||||
}
|
||||
form.uploadButton {
|
||||
width: 411px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
form .theme-undo,
|
||||
.theme-remove-bg {
|
||||
cursor: pointer;
|
||||
opacity: .3;
|
||||
padding: 7px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
right: 0px;
|
||||
visibility: visible;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
// right align
|
||||
margin-left: auto;
|
||||
}
|
||||
form .theme-undo:not([style*="display:"]) ~ .theme-remove-bg {
|
||||
// Only align the undo button if both are shown
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
input[type='text']:hover + .theme-undo,
|
||||
input[type='text'] + .theme-undo:hover,
|
||||
input[type='text']:focus + .theme-undo,
|
||||
input[type='text']:active + .theme-undo,
|
||||
input[type='url']:hover + .theme-undo,
|
||||
input[type='url'] + .theme-undo:hover,
|
||||
input[type='url']:focus + .theme-undo,
|
||||
input[type='url']:active + .theme-undo{
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
label span {
|
||||
display: inline-block;
|
||||
min-width: 175px;
|
||||
max-width: 175px;
|
||||
white-space: wrap;
|
||||
padding: 8px 0px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.icon-upload,
|
||||
.uploadButton .icon-loading-small {
|
||||
padding: 8px 20px;
|
||||
width: 20px;
|
||||
margin: 2px 0px;
|
||||
min-height: 32px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#theming_settings_status {
|
||||
height: 26px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#theming_settings_loading {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#theming_settings_msg {
|
||||
vertical-align: middle;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#theming-preview {
|
||||
width: 230px;
|
||||
height: 140px;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
text-align: center;
|
||||
margin-left: 178px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
cursor: pointer;
|
||||
background-color: var(--color-primary-default);
|
||||
background-image: var(--image-background-default, var(--image-background-plain, linear-gradient(40deg, #0082c9 0%, #30b6ff 100%)));
|
||||
|
||||
#theming-preview-logo {
|
||||
cursor: pointer;
|
||||
width: 20%;
|
||||
height: 20%;
|
||||
margin-top: 20px;
|
||||
display: inline-block;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
background-image: var(--image-logo, url('../../../core/img/logo/logo.svg'));
|
||||
}
|
||||
}
|
||||
|
||||
.theming-hints {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
height: 36px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
#theming-preview-logoheader {
|
||||
// Only using --image-logoheader to show the custom value only
|
||||
background-image: var(--image-logoheader);
|
||||
}
|
||||
|
||||
#theming-preview-favicon {
|
||||
background-image: var(--image-favicon);
|
||||
}
|
||||
|
||||
#user-theming {
|
||||
margin-top: 44px;
|
||||
display: flex;
|
||||
& > div {
|
||||
max-width: 400px;
|
||||
margin-bottom: 44px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* transition effects for theming value changes */
|
||||
#header {
|
||||
transition: background-color 500ms linear;
|
||||
svg, img {
|
||||
transition: 500ms filter linear;
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,85 @@
|
||||
/* eslint-disable node/no-unpublished-import */
|
||||
import { applyChangesToNextcloud, configureNextcloud, preppingNextcloud, startNextcloud, stopNextcloud, waitOnNextcloud } from './cypress/dockerNode'
|
||||
import { defineConfig } from 'cypress'
|
||||
|
||||
import browserify from '@cypress/browserify-preprocessor'
|
||||
|
||||
export default defineConfig({
|
||||
projectId: '37xpdh',
|
||||
|
||||
// 16/9 screen ratio
|
||||
viewportWidth: 1280,
|
||||
viewportHeight: 720,
|
||||
|
||||
// Tries again 2 more times on failure
|
||||
retries: {
|
||||
runMode: 2,
|
||||
// do not retry in `cypress open`
|
||||
openMode: 0,
|
||||
},
|
||||
|
||||
// Needed to trigger `after:run` events with cypress open
|
||||
experimentalInteractiveRunEvents: true,
|
||||
|
||||
// faster video processing
|
||||
videoCompression: false,
|
||||
|
||||
// Visual regression testing
|
||||
env: {
|
||||
failSilently: false,
|
||||
type: 'actual',
|
||||
},
|
||||
screenshotsFolder: 'cypress/snapshots/actual',
|
||||
trashAssetsBeforeRuns: true,
|
||||
|
||||
e2e: {
|
||||
// Enable session management and disable isolation
|
||||
experimentalSessionAndOrigin: true,
|
||||
testIsolation: 'off',
|
||||
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
async setupNodeEvents(on, config) {
|
||||
// Fix browserslist extend https://github.com/cypress-io/cypress/issues/2983#issuecomment-570616682
|
||||
on('file:preprocessor', browserify({ typescript: require.resolve('typescript') }))
|
||||
|
||||
// Disable spell checking to prevent rendering differences
|
||||
on('before:browser:launch', (browser, launchOptions) => {
|
||||
if (browser.family === 'chromium' && browser.name !== 'electron') {
|
||||
launchOptions.preferences.default['browser.enable_spellchecking'] = false
|
||||
return launchOptions
|
||||
}
|
||||
|
||||
if (browser.family === 'firefox') {
|
||||
launchOptions.preferences['layout.spellcheckDefault'] = 0
|
||||
return launchOptions
|
||||
}
|
||||
|
||||
if (browser.name === 'electron') {
|
||||
launchOptions.preferences.spellcheck = false
|
||||
return launchOptions
|
||||
}
|
||||
})
|
||||
|
||||
// Remove container after run
|
||||
on('after:run', () => {
|
||||
stopNextcloud()
|
||||
})
|
||||
|
||||
// Before the browser launches
|
||||
// starting Nextcloud testing container
|
||||
return startNextcloud(process.env.BRANCH)
|
||||
.then((ip) => {
|
||||
// Setting container's IP as base Url
|
||||
config.baseUrl = `http://${ip}/index.php`
|
||||
return ip
|
||||
})
|
||||
.then(waitOnNextcloud)
|
||||
.then(configureNextcloud)
|
||||
.then(applyChangesToNextcloud)
|
||||
.then(() => {
|
||||
return config
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
@ -0,0 +1,243 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable node/no-unpublished-import */
|
||||
|
||||
import Docker from 'dockerode'
|
||||
import waitOn from 'wait-on'
|
||||
import tar from 'tar'
|
||||
|
||||
export const docker = new Docker()
|
||||
|
||||
const CONTAINER_NAME = 'nextcloud-cypress-tests-server'
|
||||
const SERVER_IMAGE = 'ghcr.io/nextcloud/continuous-integration-shallow-server'
|
||||
|
||||
/**
|
||||
* Start the testing container
|
||||
*
|
||||
* @param {string} branch the branch of your current work
|
||||
*/
|
||||
export const startNextcloud = async function(branch: string = 'master'): Promise<any> {
|
||||
|
||||
try {
|
||||
// Pulling images
|
||||
console.log('\nPulling images... ⏳')
|
||||
await new Promise((resolve, reject): any => docker.pull(SERVER_IMAGE, (err, stream) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
// https://github.com/apocas/dockerode/issues/357
|
||||
docker.modem.followProgress(stream, onFinished)
|
||||
|
||||
function onFinished(err) {
|
||||
if (!err) {
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
reject(err)
|
||||
}
|
||||
}))
|
||||
console.log('└─ Done')
|
||||
|
||||
// Remove old container if exists
|
||||
console.log('\nChecking running containers... 🔍')
|
||||
try {
|
||||
const oldContainer = docker.getContainer(CONTAINER_NAME)
|
||||
const oldContainerData = await oldContainer.inspect()
|
||||
if (oldContainerData) {
|
||||
console.log('├─ Existing running container found')
|
||||
console.log('├─ Removing... ⏳')
|
||||
// Forcing any remnants to be removed just in case
|
||||
await oldContainer.remove({ force: true })
|
||||
console.log('└─ Done')
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('└─ None found!')
|
||||
}
|
||||
|
||||
// Starting container
|
||||
console.log('\nStarting Nextcloud container... 🚀')
|
||||
console.log(`├─ Using branch '${branch}'`)
|
||||
const container = await docker.createContainer({
|
||||
Image: SERVER_IMAGE,
|
||||
name: CONTAINER_NAME,
|
||||
HostConfig: {
|
||||
Binds: [],
|
||||
},
|
||||
})
|
||||
await container.start()
|
||||
|
||||
// Get container's IP
|
||||
const ip = await getContainerIP(container)
|
||||
|
||||
console.log(`├─ Nextcloud container's IP is ${ip} 🌏`)
|
||||
return ip
|
||||
} catch (err) {
|
||||
console.log('└─ Unable to start the container 🛑')
|
||||
console.log(err)
|
||||
stopNextcloud()
|
||||
throw new Error('Unable to start the container')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Nextcloud
|
||||
*/
|
||||
export const configureNextcloud = async function() {
|
||||
console.log('\nConfiguring nextcloud...')
|
||||
const container = docker.getContainer(CONTAINER_NAME)
|
||||
await runExec(container, ['php', 'occ', '--version'], true)
|
||||
|
||||
// Be consistent for screenshots
|
||||
await runExec(container, ['php', 'occ', 'config:system:set', 'default_language', '--value', 'en'], true)
|
||||
await runExec(container, ['php', 'occ', 'config:system:set', 'force_language', '--value', 'en'], true)
|
||||
await runExec(container, ['php', 'occ', 'config:system:set', 'default_locale', '--value', 'en_US'], true)
|
||||
await runExec(container, ['php', 'occ', 'config:system:set', 'force_locale', '--value', 'en_US'], true)
|
||||
await runExec(container, ['php', 'occ', 'config:system:set', 'enforce_theme', '--value', 'light'], true)
|
||||
|
||||
// Enable the app and give status
|
||||
await runExec(container, ['php', 'occ', 'app:enable', '--force', 'viewer'], true)
|
||||
// await runExec(container, ['php', 'occ', 'app:list'], true)
|
||||
|
||||
console.log('└─ Nextcloud is now ready to use 🎉')
|
||||
}
|
||||
|
||||
/**
|
||||
* Applying local changes to the container
|
||||
* Only triggered if we're not in CI. Otherwise the
|
||||
* continuous-integration-shallow-server image will
|
||||
* already fetch the proper branch.
|
||||
*/
|
||||
export const applyChangesToNextcloud = async function() {
|
||||
console.log('\nApply local changes to nextcloud...')
|
||||
const container = docker.getContainer(CONTAINER_NAME)
|
||||
|
||||
const htmlPath = '/var/www/html'
|
||||
const folderPaths = [
|
||||
'./apps',
|
||||
'./core',
|
||||
'./dist',
|
||||
'./lib',
|
||||
'./ocs',
|
||||
]
|
||||
|
||||
// Tar-streaming the above folder sinto the container
|
||||
const serverTar = tar.c({ gzip: false }, folderPaths)
|
||||
await container.putArchive(serverTar, {
|
||||
path: htmlPath,
|
||||
})
|
||||
|
||||
// Making sure we have the proper permissions
|
||||
await runExec(container, ['chown', '-R', 'www-data:www-data', htmlPath], false, 'root')
|
||||
|
||||
console.log('└─ Changes applied successfully 🎉')
|
||||
}
|
||||
|
||||
/**
|
||||
* Force stop the testing container
|
||||
*/
|
||||
export const stopNextcloud = async function() {
|
||||
try {
|
||||
const container = docker.getContainer(CONTAINER_NAME)
|
||||
console.log('Stopping Nextcloud container...')
|
||||
container.remove({ force: true })
|
||||
console.log('└─ Nextcloud container removed 🥀')
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the testing container's IP
|
||||
*
|
||||
* @param {Docker.Container} container the container to get the IP from
|
||||
*/
|
||||
export const getContainerIP = async function(
|
||||
container = docker.getContainer(CONTAINER_NAME)
|
||||
): Promise<string> {
|
||||
let ip = ''
|
||||
let tries = 0
|
||||
while (ip === '' && tries < 10) {
|
||||
tries++
|
||||
|
||||
await container.inspect(function(err, data) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
ip = data?.NetworkSettings?.IPAddress || ''
|
||||
})
|
||||
|
||||
if (ip !== '') {
|
||||
break
|
||||
}
|
||||
|
||||
await sleep(1000 * tries)
|
||||
}
|
||||
|
||||
return ip
|
||||
}
|
||||
|
||||
// Would be simpler to start the container from cypress.config.ts,
|
||||
// but when checking out different branches, it can take a few seconds
|
||||
// Until we can properly configure the baseUrl retry intervals,
|
||||
// We need to make sure the server is already running before cypress
|
||||
// https://github.com/cypress-io/cypress/issues/22676
|
||||
export const waitOnNextcloud = async function(ip: string) {
|
||||
console.log('├─ Waiting for Nextcloud to be ready... ⏳')
|
||||
await waitOn({ resources: [`http://${ip}/index.php`] })
|
||||
console.log('└─ Done')
|
||||
}
|
||||
|
||||
const runExec = async function(
|
||||
container: Docker.Container,
|
||||
command: string[],
|
||||
verbose = false,
|
||||
user = 'www-data'
|
||||
) {
|
||||
const exec = await container.exec({
|
||||
Cmd: command,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
User: user,
|
||||
})
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
exec.start({}, (err, stream) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
if (stream) {
|
||||
stream.setEncoding('utf-8')
|
||||
stream.on('data', str => {
|
||||
if (verbose && str.trim() !== '') {
|
||||
console.log(`├─ ${str.trim().replace(/\n/gi, '\n├─ ')}`)
|
||||
}
|
||||
})
|
||||
stream.on('end', resolve)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const sleep = function(milliseconds: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, milliseconds))
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
describe('Login with a new user and open the files app', function() {
|
||||
before(function() {
|
||||
cy.createRandomUser().then((user) => {
|
||||
cy.login(user)
|
||||
})
|
||||
})
|
||||
|
||||
after(function() {
|
||||
cy.logout()
|
||||
})
|
||||
|
||||
it('See the default file welcome.txt in the files list', function() {
|
||||
cy.visit('/apps/files')
|
||||
cy.get('.files-fileList tr').should('contain', 'welcome.txt')
|
||||
})
|
||||
})
|
@ -0,0 +1,164 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @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 type { User } from '@nextcloud/cypress'
|
||||
|
||||
const defaultPrimary = '#006aa3'
|
||||
const defaultBackground = 'kamil-porembinski-clouds.jpg'
|
||||
|
||||
const validateThemingCss = function(expectedPrimary = '#0082c9', expectedBackground = 'kamil-porembinski-clouds.jpg', bright = false) {
|
||||
return cy.window().then((win) => {
|
||||
const primary = getComputedStyle(win.document.body).getPropertyValue('--color-primary')
|
||||
const background = getComputedStyle(win.document.body).getPropertyValue('--image-background')
|
||||
const invertIfBright = getComputedStyle(win.document.body).getPropertyValue('--background-image-invert-if-bright')
|
||||
|
||||
// Returning boolean for cy.waitUntil usage
|
||||
return primary === expectedPrimary
|
||||
&& background.includes(expectedBackground)
|
||||
&& invertIfBright === (bright ? 'invert(100%)' : 'no')
|
||||
})
|
||||
}
|
||||
|
||||
describe('User default background settings', function() {
|
||||
before(function() {
|
||||
cy.createRandomUser().then((user: User) => {
|
||||
cy.login(user)
|
||||
})
|
||||
})
|
||||
|
||||
it('See the user background settings', function() {
|
||||
cy.visit('/settings/user/theming')
|
||||
cy.get('[data-user-theming-background-settings]').scrollIntoView().should('be.visible')
|
||||
})
|
||||
|
||||
// Default cloud background is not rendered if admin theming background remains unchanged
|
||||
it('Default cloud background is not rendered', function() {
|
||||
cy.get(`[data-user-theming-background-shipped="${defaultBackground}"]`).should('not.exist')
|
||||
})
|
||||
|
||||
it('Default is selected on new users', function() {
|
||||
cy.get('[data-user-theming-background-default]').should('be.visible')
|
||||
cy.get('[data-user-theming-background-default]').should('have.class', 'background--active')
|
||||
})
|
||||
})
|
||||
|
||||
describe('User select shipped backgrounds', function() {
|
||||
before(function() {
|
||||
cy.createRandomUser().then((user: User) => {
|
||||
cy.login(user)
|
||||
})
|
||||
})
|
||||
|
||||
it('See the user background settings', function() {
|
||||
cy.visit('/settings/user/theming')
|
||||
cy.get('[data-user-theming-background-settings]').scrollIntoView().should('be.visible')
|
||||
})
|
||||
|
||||
it('Select a shipped background', function() {
|
||||
const background = 'anatoly-mikhaltsov-butterfly-wing-scale.jpg'
|
||||
cy.intercept('*/apps/theming/background/shipped').as('setBackground')
|
||||
|
||||
// Select background
|
||||
cy.get(`[data-user-theming-background-shipped="${background}"]`).click()
|
||||
|
||||
// Validate changed background and primary
|
||||
cy.wait('@setBackground')
|
||||
cy.waitUntil(() => validateThemingCss('#a53c17', background))
|
||||
})
|
||||
|
||||
it('Select a bright shipped background', function() {
|
||||
const background = 'bernie-cetonia-aurata-take-off-composition.jpg'
|
||||
cy.intercept('*/apps/theming/background/shipped').as('setBackground')
|
||||
|
||||
// Select background
|
||||
cy.get(`[data-user-theming-background-shipped="${background}"]`).click()
|
||||
|
||||
// Validate changed background and primary
|
||||
cy.wait('@setBackground')
|
||||
cy.waitUntil(() => validateThemingCss('#56633d', background, true))
|
||||
})
|
||||
|
||||
it('Remove background', function() {
|
||||
cy.intercept('*/apps/theming/background/custom').as('clearBackground')
|
||||
|
||||
// Clear background
|
||||
cy.get('[data-user-theming-background-clear]').click()
|
||||
|
||||
// Validate clear background
|
||||
cy.wait('@clearBackground')
|
||||
cy.waitUntil(() => validateThemingCss('#56633d', ''))
|
||||
})
|
||||
})
|
||||
|
||||
describe('User select a custom color', function() {
|
||||
before(function() {
|
||||
cy.createRandomUser().then((user: User) => {
|
||||
cy.login(user)
|
||||
})
|
||||
})
|
||||
|
||||
it('See the user background settings', function() {
|
||||
cy.visit('/settings/user/theming')
|
||||
cy.get('[data-user-theming-background-settings]').scrollIntoView().should('be.visible')
|
||||
})
|
||||
|
||||
it('Select a custom color', function() {
|
||||
cy.intercept('*/apps/theming/background/color').as('setColor')
|
||||
|
||||
cy.get('[data-user-theming-background-color]').click()
|
||||
cy.get('.color-picker__simple-color-circle:eq(3)').click()
|
||||
|
||||
// Validate clear background
|
||||
cy.wait('@setColor')
|
||||
cy.waitUntil(() => cy.window().then((win) => {
|
||||
const primary = getComputedStyle(win.document.body).getPropertyValue('--color-primary')
|
||||
return primary !== defaultPrimary
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('User select a custom background', function() {
|
||||
const image = 'image.jpg'
|
||||
before(function() {
|
||||
cy.createRandomUser().then((user: User) => {
|
||||
cy.uploadFile(user, image, 'image/jpeg')
|
||||
cy.login(user)
|
||||
})
|
||||
})
|
||||
|
||||
it('See the user background settings', function() {
|
||||
cy.visit('/settings/user/theming')
|
||||
cy.get('[data-user-theming-background-settings]').scrollIntoView().should('be.visible')
|
||||
})
|
||||
|
||||
it('Select a custom background', function() {
|
||||
cy.intercept('*/apps/theming/background/custom').as('setBackground')
|
||||
|
||||
// Pick background
|
||||
cy.get('[data-user-theming-background-custom]').click()
|
||||
cy.get(`#picker-filestable tr[data-entryname="${image}"]`).click()
|
||||
cy.get('#oc-dialog-filepicker-content ~ .oc-dialog-buttonrow button.primary').click()
|
||||
|
||||
// Wait for background to be set
|
||||
cy.wait('@setBackground')
|
||||
cy.waitUntil(() => validateThemingCss('#4c0c04', 'apps/theming/background?v='))
|
||||
})
|
||||
})
|
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
/* eslint-disable node/no-unpublished-import */
|
||||
import axios from '@nextcloud/axios'
|
||||
import { addCommands, type User} from '@nextcloud/cypress'
|
||||
import { basename } from 'path'
|
||||
|
||||
// Add custom commands
|
||||
import 'cypress-wait-until'
|
||||
addCommands()
|
||||
|
||||
// Register this file's custom commands types
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable<Subject = any> {
|
||||
uploadFile(user: User, fixture: string, mimeType: string, target ?: string): Cypress.Chainable<void>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const url = (Cypress.config('baseUrl') || '').replace(/\/index.php\/?$/g, '')
|
||||
Cypress.env('baseUrl', url)
|
||||
|
||||
/**
|
||||
* cy.uploadedFile - uploads a file from the fixtures folder
|
||||
* TODO: standardise in @nextcloud/cypress
|
||||
*
|
||||
* @param {User} user the owner of the file, e.g. admin
|
||||
* @param {string} fixture the fixture file name, e.g. image1.jpg
|
||||
* @param {string} mimeType e.g. image/png
|
||||
* @param {string} [target] the target of the file relative to the user root
|
||||
*/
|
||||
Cypress.Commands.add('uploadFile', (user, fixture, mimeType, target = `/${fixture}`) => {
|
||||
cy.clearCookies()
|
||||
const fileName = basename(target)
|
||||
|
||||
// get fixture
|
||||
return cy.fixture(fixture, 'base64').then(async file => {
|
||||
// convert the base64 string to a blob
|
||||
const blob = Cypress.Blob.base64StringToBlob(file, mimeType)
|
||||
|
||||
// Process paths
|
||||
const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}`
|
||||
const filePath = target.split('/').map(encodeURIComponent).join('/')
|
||||
try {
|
||||
const file = new File([blob], fileName, { type: mimeType })
|
||||
await axios({
|
||||
url: `${rootPath}${filePath}`,
|
||||
method: 'PUT',
|
||||
data: file,
|
||||
headers: {
|
||||
'Content-Type': mimeType,
|
||||
},
|
||||
auth: {
|
||||
username: user.userId,
|
||||
password: user.password,
|
||||
},
|
||||
}).then(response => {
|
||||
cy.log(`Uploaded ${fixture} as ${fileName}`, response)
|
||||
})
|
||||
} catch (error) {
|
||||
cy.log('error', error)
|
||||
throw new Error(`Unable to process fixture ${fixture}`)
|
||||
}
|
||||
})
|
||||
})
|
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @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 './commands'
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"include": ["./**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"types": ["cypress", "dockerode", "cypress-wait-until"],
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.json",
|
||||
"include": ["./**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"types": ["node"],
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"moduleResolution": "node",
|
||||
"target": "ESNext",
|
||||
"module": "esnext",
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"ts-node": {
|
||||
// these options are overrides used only by ts-node
|
||||
// same as our --compilerOptions flag and our TS_NODE_COMPILER_OPTIONS environment variable
|
||||
"compilerOptions": {
|
||||
"module": "commonjs"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue