Compare commits

..

3 Commits

Author SHA1 Message Date
a552e4d766 Toasts (#45)
* locales

* add toasts and update deps

* changelog

* lock file
2022-07-20 10:41:37 +02:00
c3b1772728 Testing (#44)
* testing

* try playwrigth

* testing

* add pr support

* not on each commit

* add test ids

* make backend more configuratble

* 2.0.2

* spec
2022-07-19 21:55:05 +02:00
786878a3e4 Testing (#41)
* testing

* try playwrigth

* testing

* add pr support

* not on each commit
2022-07-19 14:12:51 +02:00
45 changed files with 579 additions and 170 deletions

1
.gitattributes vendored
View File

@@ -1 +1,2 @@
*.afdesign filter=lfs diff=lfs merge=lfs -text
test/assets/** filter=lfs diff=lfs merge=lfs -text

36
.github/workflows/test.yaml vendored Normal file
View File

@@ -0,0 +1,36 @@
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
services:
redis:
image: redis:7-alpine
ports:
- 6379:6379
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: "16"
- uses: pnpm/action-setup@v2
with:
version: 7
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.61
- name: Prepare
run: |
pnpm install
pnpm run ci:prepare
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run your tests
run: pnpm run test
- name: Upload test results
if: always()
uses: actions/upload-artifact@v2
with:
name: playwright-report
path: playwright-report

3
.gitignore vendored
View File

@@ -9,3 +9,6 @@ node_modules
/build
/functions
.env
General
test-results

View File

@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.2] - 2022-07-20
### Added
- Toasts for events.
- E2E Tests.
- Make backend more configurable
## [2.0.1] - 2022-07-18
### Added

View File

@@ -12,6 +12,8 @@ RUN pnpm run build
FROM rust:1.61-alpine as backend
WORKDIR /tmp
RUN apk add libc-dev openssl-dev alpine-sdk
COPY ./backend/Cargo.* ./
RUN cargo fetch
COPY ./backend ./
RUN cargo build --release
@@ -20,7 +22,8 @@ RUN cargo build --release
FROM alpine
WORKDIR /app
COPY --from=backend /tmp/target/release/cryptgeon .
COPY --from=client /tmp/build ./frontend/build
ENV REDIS=redis://redis/
COPY --from=client /tmp/build ./frontend
ENV FRONTEND_PATH="./frontend"
ENV REDIS="redis://redis/"
EXPOSE 5000
ENTRYPOINT [ "/app/cryptgeon" ]

View File

@@ -165,6 +165,25 @@ Running `pnpm run dev` in the root folder will start the following things:
You can see the app under [localhost:1234](http://localhost:1234).
## Tests
Tests are end to end tests written with Playwright.
```sh
pnpm run ci:prepare
docker compose up redis -d
pnpm run ci:server
# In another terminal.
# Use the test or test:local script. The local version only runs in one browser for quicker development.
pnpm run test:local
```
###### Attributions
Icons made by <a href="https://www.freepik.com" title="Freepik">freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
- Test data:
- Text for tests [Nietzsche Ipsum](https://nietzsche-ipsum.com/)
- [AES Paper](https://www.cs.miami.edu/home/burt/learning/Csc688.012/rijndael/rijndael_doc_V2.pdf)
- [Unsplash Pictures](https://unsplash.com/)
- Loading animation by [Nikhil Krishnan](https://codepen.io/nikhil8krishnan/pen/rVoXJa)
- Icons made by <a href="https://www.freepik.com" title="Freepik">freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>

2
backend/Cargo.lock generated
View File

@@ -424,7 +424,7 @@ dependencies = [
[[package]]
name = "cryptgeon"
version = "2.0.1"
version = "2.0.2"
dependencies = [
"actix-files",
"actix-web",

View File

@@ -1,6 +1,6 @@
[package]
name = "cryptgeon"
version = "2.0.1"
version = "2.0.2"
authors = ["cupcakearmy <hi@nicco.io>"]
edition = "2021"

View File

@@ -1,14 +1,17 @@
use actix_files::{Files, NamedFile};
use actix_web::{web, Result};
use crate::config;
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service(
Files::new("/", "./frontend/build")
Files::new("/", config::FRONTEND_PATH.to_string())
.index_file("index.html")
.use_etag(true),
);
}
pub async fn index() -> Result<NamedFile> {
Ok(NamedFile::open("./frontend/build/index.html")?)
let index = format!("{}{}", config::FRONTEND_PATH.to_string(), "/index.html");
Ok(NamedFile::open(index)?)
}

View File

@@ -1,10 +1,14 @@
use byte_unit::Byte;
// General
// Internal
lazy_static! {
pub static ref VERSION: String = option_env!("CARGO_PKG_VERSION")
.unwrap_or("Unknown")
.to_string();
pub static ref FRONTEND_PATH: String =
std::env::var("FRONTEND_PATH").unwrap_or("../frontend/build".to_string());
pub static ref LISTEN_ADDR: String =
std::env::var("LISTEN_ADDR").unwrap_or("0.0.0.0:5000".to_string());
}
// CONFIG

View File

@@ -29,7 +29,7 @@ async fn main() -> std::io::Result<()> {
.configure(client::init)
.default_service(web::to(client::index))
})
.bind("0.0.0.0:5000")?
.bind(config::LISTEN_ADDR.to_string())?
.run()
.await;
}

View File

@@ -16,4 +16,4 @@ services:
environment:
SIZE_LIMIT: 128 MiB
ports:
- 80:5000
- 1234:5000

View File

@@ -11,6 +11,7 @@
"max": "max",
"share_link": "Link teilen",
"copy_clipboard": "in die Zwischenablage kopieren",
"copied_to_clipboard": "in die Zwischenablage kopiert",
"encrypting": "verschlüsseln",
"decrypting": "entschlüsselt",
"uploading": "hochladen",
@@ -27,7 +28,9 @@
"max": "max: {n}",
"empty_content": "Notiz ist leer."
},
"copied_to_clipboard": "in die Zwischenablage kopiert 🔗"
"messages": {
"note_created": "notiz erstellt."
}
},
"show": {
"errors": {

View File

@@ -11,6 +11,7 @@
"max": "max",
"share_link": "share link",
"copy_clipboard": "copy to clipboard",
"copied_to_clipboard": "copied to clipboard",
"encrypting": "encrypting",
"decrypting": "decrypting",
"uploading": "uploading",
@@ -27,7 +28,9 @@
"max": "max: {n}",
"empty_content": "note is empty."
},
"copied_to_clipboard": "copied to clipboard 🔗"
"messages": {
"note_created": "note created."
}
},
"show": {
"errors": {

View File

@@ -11,6 +11,7 @@
"max": "max",
"share_link": "compartir enlace",
"copy_clipboard": "copiar al portapapeles",
"copied_to_clipboard": "copiado al portapapeles",
"encrypting": "encriptando",
"decrypting": "descifrando",
"uploading": "cargando",
@@ -27,7 +28,9 @@
"max": "max: {n}",
"empty_content": "la nota está vacía."
},
"copied_to_clipboard": "copiado al portapapeles 🔗"
"messages": {
"note_created": "nota creada."
}
},
"show": {
"errors": {

View File

@@ -11,6 +11,7 @@
"max": "max",
"share_link": "partager le lien",
"copy_clipboard": "copier dans le presse-papiers",
"copied_to_clipboard": "copié dans le presse-papiers",
"encrypting": "cryptage",
"decrypting": "déchiffrer",
"uploading": "téléchargement",
@@ -27,7 +28,9 @@
"max": "max: {n}",
"empty_content": "La note est vide."
},
"copied_to_clipboard": "copié dans le presse-papiers 🔗"
"messages": {
"note_created": "note créée."
}
},
"show": {
"errors": {

View File

@@ -11,6 +11,7 @@
"max": "max",
"share_link": "condividi link",
"copy_clipboard": "copia negli appunti",
"copied_to_clipboard": "copiato negli appunti",
"encrypting": "criptando",
"decrypting": "decifrando",
"uploading": "caricamento",
@@ -27,7 +28,9 @@
"max": "max: {n}",
"empty_content": "la nota è vuota."
},
"copied_to_clipboard": "copiato negli appunti 🔗"
"messages": {
"note_created": "nota creata."
}
},
"show": {
"errors": {

View File

@@ -11,6 +11,7 @@
"max": "最大值",
"share_link": "分享链接",
"copy_clipboard": "复制到剪切版",
"copied_to_clipboard": "已复制到剪切板",
"encrypting": "加密",
"decrypting": "解密",
"uploading": "上传",
@@ -27,7 +28,9 @@
"max": "最大文件大小: {n}",
"empty_content": "密信为空!"
},
"copied_to_clipboard": "已复制到剪切板 🔗"
"messages": {
"note_created": "注释创建。"
}
},
"show": {
"errors": {

View File

@@ -3,7 +3,7 @@
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "svelte-kit preview",
"preview": "vite preview --port 3000",
"check": "svelte-check --tsconfig tsconfig.json",
"licenses": "license-checker --summary > licenses.csv",
"locale:download": "node scripts/locale.js"
@@ -11,10 +11,11 @@
"type": "module",
"devDependencies": {
"@lokalise/node-api": "^7.3.1",
"@sveltejs/adapter-static": "^1.0.0-next.37",
"@sveltejs/kit": "^1.0.0-next.377",
"@sveltejs/adapter-static": "^1.0.0-next.38",
"@sveltejs/kit": "^1.0.0-next.384",
"@types/dompurify": "^2.3.3",
"@types/file-saver": "^2.0.5",
"@zerodevx/svelte-toast": "^0.7.2",
"adm-zip": "^0.5.9",
"dotenv": "^16.0.1",
"svelte": "^3.49.0",
@@ -23,12 +24,12 @@
"svelte-preprocess": "^4.10.7",
"tslib": "^2.4.0",
"typescript": "^4.7.4",
"vite": "^3.0.1"
"vite": "^3.0.2"
},
"dependencies": {
"@fontsource/fira-mono": "^4.5.8",
"copy-to-clipboard": "^3.3.1",
"dompurify": "^2.3.9",
"dompurify": "^2.3.10",
"file-saver": "^2.0.5",
"pretty-bytes": "^5.6.0"
}

168
frontend/pnpm-lock.yaml generated
View File

@@ -3,13 +3,14 @@ lockfileVersion: 5.4
specifiers:
'@fontsource/fira-mono': ^4.5.8
'@lokalise/node-api': ^7.3.1
'@sveltejs/adapter-static': ^1.0.0-next.37
'@sveltejs/kit': ^1.0.0-next.377
'@sveltejs/adapter-static': ^1.0.0-next.38
'@sveltejs/kit': ^1.0.0-next.384
'@types/dompurify': ^2.3.3
'@types/file-saver': ^2.0.5
'@zerodevx/svelte-toast': ^0.7.2
adm-zip: ^0.5.9
copy-to-clipboard: ^3.3.1
dompurify: ^2.3.9
dompurify: ^2.3.10
dotenv: ^16.0.1
file-saver: ^2.0.5
pretty-bytes: ^5.6.0
@@ -19,21 +20,22 @@ specifiers:
svelte-preprocess: ^4.10.7
tslib: ^2.4.0
typescript: ^4.7.4
vite: ^3.0.1
vite: ^3.0.2
dependencies:
'@fontsource/fira-mono': 4.5.8
copy-to-clipboard: 3.3.1
dompurify: 2.3.9
dompurify: 2.3.10
file-saver: 2.0.5
pretty-bytes: 5.6.0
devDependencies:
'@lokalise/node-api': 7.3.1
'@sveltejs/adapter-static': 1.0.0-next.37
'@sveltejs/kit': 1.0.0-next.377_svelte@3.49.0+vite@3.0.1
'@sveltejs/adapter-static': 1.0.0-next.38
'@sveltejs/kit': 1.0.0-next.384_svelte@3.49.0+vite@3.0.2
'@types/dompurify': 2.3.3
'@types/file-saver': 2.0.5
'@zerodevx/svelte-toast': 0.7.2
adm-zip: 0.5.9
dotenv: 16.0.1
svelte: 3.49.0
@@ -42,7 +44,7 @@ devDependencies:
svelte-preprocess: 4.10.7_uslzfc62di2n2otc2tvfklnwji
tslib: 2.4.0
typescript: 4.7.4
vite: 3.0.1
vite: 3.0.2
packages:
@@ -66,20 +68,20 @@ packages:
engines: {node: '>=6.9.0'}
dev: true
/@babel/core/7.18.6:
resolution: {integrity: sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==}
/@babel/core/7.18.9:
resolution: {integrity: sha512-1LIb1eL8APMy91/IMW+31ckrfBM4yCoLaVzoDhZUKSM4cu1L1nIidyxkCgzPAgrC5WEz36IPEr/eSeSF9pIn+g==}
engines: {node: '>=6.9.0'}
dependencies:
'@ampproject/remapping': 2.2.0
'@babel/code-frame': 7.18.6
'@babel/generator': 7.18.7
'@babel/helper-compilation-targets': 7.18.6_@babel+core@7.18.6
'@babel/helper-module-transforms': 7.18.8
'@babel/helpers': 7.18.6
'@babel/parser': 7.18.8
'@babel/generator': 7.18.9
'@babel/helper-compilation-targets': 7.18.9_@babel+core@7.18.9
'@babel/helper-module-transforms': 7.18.9
'@babel/helpers': 7.18.9
'@babel/parser': 7.18.9
'@babel/template': 7.18.6
'@babel/traverse': 7.18.8
'@babel/types': 7.18.8
'@babel/traverse': 7.18.9
'@babel/types': 7.18.9
convert-source-map: 1.8.0
debug: 4.3.4
gensync: 1.0.0-beta.2
@@ -89,73 +91,73 @@ packages:
- supports-color
dev: true
/@babel/generator/7.18.7:
resolution: {integrity: sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==}
/@babel/generator/7.18.9:
resolution: {integrity: sha512-wt5Naw6lJrL1/SGkipMiFxJjtyczUWTP38deiP1PO60HsBjDeKk08CGC3S8iVuvf0FmTdgKwU1KIXzSKL1G0Ug==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.8
'@babel/types': 7.18.9
'@jridgewell/gen-mapping': 0.3.2
jsesc: 2.5.2
dev: true
/@babel/helper-compilation-targets/7.18.6_@babel+core@7.18.6:
resolution: {integrity: sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg==}
/@babel/helper-compilation-targets/7.18.9_@babel+core@7.18.9:
resolution: {integrity: sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
'@babel/compat-data': 7.18.8
'@babel/core': 7.18.6
'@babel/core': 7.18.9
'@babel/helper-validator-option': 7.18.6
browserslist: 4.21.2
semver: 6.3.0
dev: true
/@babel/helper-environment-visitor/7.18.6:
resolution: {integrity: sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q==}
/@babel/helper-environment-visitor/7.18.9:
resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-function-name/7.18.6:
resolution: {integrity: sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw==}
/@babel/helper-function-name/7.18.9:
resolution: {integrity: sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.18.6
'@babel/types': 7.18.8
'@babel/types': 7.18.9
dev: true
/@babel/helper-hoist-variables/7.18.6:
resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.8
'@babel/types': 7.18.9
dev: true
/@babel/helper-module-imports/7.18.6:
resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.8
'@babel/types': 7.18.9
dev: true
/@babel/helper-module-transforms/7.18.8:
resolution: {integrity: sha512-che3jvZwIcZxrwh63VfnFTUzcAM9v/lznYkkRxIBGMPt1SudOKHAEec0SIRCfiuIzTcF7VGj/CaTT6gY4eWxvA==}
/@babel/helper-module-transforms/7.18.9:
resolution: {integrity: sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-environment-visitor': 7.18.6
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-module-imports': 7.18.6
'@babel/helper-simple-access': 7.18.6
'@babel/helper-split-export-declaration': 7.18.6
'@babel/helper-validator-identifier': 7.18.6
'@babel/template': 7.18.6
'@babel/traverse': 7.18.8
'@babel/types': 7.18.8
'@babel/traverse': 7.18.9
'@babel/types': 7.18.9
transitivePeerDependencies:
- supports-color
dev: true
/@babel/helper-plugin-utils/7.18.6:
resolution: {integrity: sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg==}
/@babel/helper-plugin-utils/7.18.9:
resolution: {integrity: sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==}
engines: {node: '>=6.9.0'}
dev: true
@@ -163,14 +165,14 @@ packages:
resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.8
'@babel/types': 7.18.9
dev: true
/@babel/helper-split-export-declaration/7.18.6:
resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.8
'@babel/types': 7.18.9
dev: true
/@babel/helper-validator-identifier/7.18.6:
@@ -183,13 +185,13 @@ packages:
engines: {node: '>=6.9.0'}
dev: true
/@babel/helpers/7.18.6:
resolution: {integrity: sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ==}
/@babel/helpers/7.18.9:
resolution: {integrity: sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.18.6
'@babel/traverse': 7.18.8
'@babel/types': 7.18.8
'@babel/traverse': 7.18.9
'@babel/types': 7.18.9
transitivePeerDependencies:
- supports-color
dev: true
@@ -203,12 +205,12 @@ packages:
js-tokens: 4.0.0
dev: true
/@babel/parser/7.18.8:
resolution: {integrity: sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA==}
/@babel/parser/7.18.9:
resolution: {integrity: sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.18.8
'@babel/types': 7.18.9
dev: true
/@babel/template/7.18.6:
@@ -216,30 +218,30 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.18.6
'@babel/parser': 7.18.8
'@babel/types': 7.18.8
'@babel/parser': 7.18.9
'@babel/types': 7.18.9
dev: true
/@babel/traverse/7.18.8:
resolution: {integrity: sha512-UNg/AcSySJYR/+mIcJQDCv00T+AqRO7j/ZEJLzpaYtgM48rMg5MnkJgyNqkzo88+p4tfRvZJCEiwwfG6h4jkRg==}
/@babel/traverse/7.18.9:
resolution: {integrity: sha512-LcPAnujXGwBgv3/WHv01pHtb2tihcyW1XuL9wd7jqh1Z8AQkTd+QVjMrMijrln0T7ED3UXLIy36P9Ao7W75rYg==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.18.6
'@babel/generator': 7.18.7
'@babel/helper-environment-visitor': 7.18.6
'@babel/helper-function-name': 7.18.6
'@babel/generator': 7.18.9
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-function-name': 7.18.9
'@babel/helper-hoist-variables': 7.18.6
'@babel/helper-split-export-declaration': 7.18.6
'@babel/parser': 7.18.8
'@babel/types': 7.18.8
'@babel/parser': 7.18.9
'@babel/types': 7.18.9
debug: 4.3.4
globals: 11.12.0
transitivePeerDependencies:
- supports-color
dev: true
/@babel/types/7.18.8:
resolution: {integrity: sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw==}
/@babel/types/7.18.9:
resolution: {integrity: sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-validator-identifier': 7.18.6
@@ -357,31 +359,31 @@ packages:
engines: {node: '>=10'}
dev: true
/@sveltejs/adapter-static/1.0.0-next.37:
resolution: {integrity: sha512-BDFkx4CGAd6pG4e3+zYjy/eM9UDbhkRgXqavUzCO5oT8xXao5TeprY1AIbdzjMTmFjsWdeSXE9TbIsT0iikpyQ==}
/@sveltejs/adapter-static/1.0.0-next.38:
resolution: {integrity: sha512-O1b264K62E3OrUnsFxMjKn3CUJF50fxGcW0rWk8fa5kjzskPsSyTxS3jnWNryFaVJ3oSUtx57m4qFW43S1910Q==}
dependencies:
tiny-glob: 0.2.9
dev: true
/@sveltejs/kit/1.0.0-next.377_svelte@3.49.0+vite@3.0.1:
resolution: {integrity: sha512-DH2v2yUBUuDZ7vzjPXUd/yt1AMR3BIkZN0ubLAvS2C+q5Wbvk7ZvAJhfPZ3OYc3ZpQXe4ZGEcptOjvEYvd1lLA==}
/@sveltejs/kit/1.0.0-next.384_svelte@3.49.0+vite@3.0.2:
resolution: {integrity: sha512-m1i9dhma1JAuDYp8nrPyqsN6VWv/ye90h6mWQvAC3lCNmKlb4F8cxhMYIhGYgzaZYSqToNiTaK2QRVPuB4i2/g==}
engines: {node: '>=16.9'}
hasBin: true
peerDependencies:
svelte: ^3.44.0
vite: ^3.0.0
dependencies:
'@sveltejs/vite-plugin-svelte': 1.0.1_svelte@3.49.0+vite@3.0.1
'@sveltejs/vite-plugin-svelte': 1.0.1_svelte@3.49.0+vite@3.0.2
chokidar: 3.5.3
sade: 1.8.1
svelte: 3.49.0
vite: 3.0.1
vite: 3.0.2
transitivePeerDependencies:
- diff-match-patch
- supports-color
dev: true
/@sveltejs/vite-plugin-svelte/1.0.1_svelte@3.49.0+vite@3.0.1:
/@sveltejs/vite-plugin-svelte/1.0.1_svelte@3.49.0+vite@3.0.2:
resolution: {integrity: sha512-PorCgUounn0VXcpeJu+hOweZODKmGuLHsLomwqSj+p26IwjjGffmYQfVHtiTWq+NqaUuuHWWG7vPge6UFw4Aeg==}
engines: {node: ^14.18.0 || >= 16}
peerDependencies:
@@ -399,7 +401,7 @@ packages:
magic-string: 0.26.2
svelte: 3.49.0
svelte-hmr: 0.14.12_svelte@3.49.0
vite: 3.0.1
vite: 3.0.2
transitivePeerDependencies:
- supports-color
dev: true
@@ -468,6 +470,10 @@ packages:
resolution: {integrity: sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==}
dev: true
/@zerodevx/svelte-toast/0.7.2:
resolution: {integrity: sha512-vWiY6IqsstcOoQ8PFBuFuxgPkj1JFAGhUF9gC7wLx7c5A9SSfdtxWs/39ekGSIeyJK0yqWhTcmzGrCEWSELzDw==}
dev: true
/adm-zip/0.5.9:
resolution: {integrity: sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==}
engines: {node: '>=6.0'}
@@ -495,8 +501,8 @@ packages:
/babel-plugin-precompile-intl/0.4.0:
resolution: {integrity: sha512-kfZPPgjutWg7nPxvwHscgxdhiOUEgWI+MZwh7NZ8lIAqf/tVKzuaoVNC4Bnl4pgzCMpuRktQz2bwFypF2ehJWg==}
dependencies:
'@babel/core': 7.18.6
'@babel/helper-plugin-utils': 7.18.6
'@babel/core': 7.18.9
'@babel/helper-plugin-utils': 7.18.9
'@formatjs/icu-messageformat-parser': 2.1.4
transitivePeerDependencies:
- supports-color
@@ -531,9 +537,9 @@ packages:
hasBin: true
dependencies:
caniuse-lite: 1.0.30001367
electron-to-chromium: 1.4.192
electron-to-chromium: 1.4.195
node-releases: 2.0.6
update-browserslist-db: 1.0.4_browserslist@4.21.2
update-browserslist-db: 1.0.5_browserslist@4.21.2
dev: true
/buffer-crc32/0.2.13:
@@ -555,7 +561,7 @@ packages:
keyv: 4.3.3
lowercase-keys: 2.0.0
normalize-url: 6.1.0
responselike: 2.0.0
responselike: 2.0.1
dev: true
/callsites/3.1.0:
@@ -665,8 +671,8 @@ packages:
engines: {node: '>=8'}
dev: true
/dompurify/2.3.9:
resolution: {integrity: sha512-3zOnuTwup4lPV/GfGS6UzG4ub9nhSYagR/5tB3AvDEwqyy5dtyCM2dVjwGDCnrPerXifBKTYh/UWCGKK7ydhhw==}
/dompurify/2.3.10:
resolution: {integrity: sha512-o7Fg/AgC7p/XpKjf/+RC3Ok6k4St5F7Q6q6+Nnm3p2zGWioAY6dh0CbbuwOhH2UcSzKsdniE/YnE2/92JcsA+g==}
dev: false
/dotenv/16.0.1:
@@ -674,8 +680,8 @@ packages:
engines: {node: '>=12'}
dev: true
/electron-to-chromium/1.4.192:
resolution: {integrity: sha512-8nCXyIQY9An88NXAp+PuPy5h3/w5ZY7Iu2lag65Q0XREprcat5F8gKhoHsBUnQcFuCRnmevpR8yEBYRU3d2HDw==}
/electron-to-chromium/1.4.195:
resolution: {integrity: sha512-vefjEh0sk871xNmR5whJf9TEngX+KTKS3hOHpjoMpauKkwlGwtMz1H8IaIjAT/GNnX0TbGwAdmVoXCAzXf+PPg==}
dev: true
/end-of-stream/1.4.4:
@@ -1011,7 +1017,7 @@ packages:
http2-wrapper: 1.0.3
lowercase-keys: 2.0.0
p-cancelable: 2.1.1
responselike: 2.0.0
responselike: 2.0.1
dev: true
/graceful-fs/4.2.10:
@@ -1326,8 +1332,8 @@ packages:
supports-preserve-symlinks-flag: 1.0.0
dev: true
/responselike/2.0.0:
resolution: {integrity: sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==}
/responselike/2.0.1:
resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==}
dependencies:
lowercase-keys: 2.0.0
dev: true
@@ -1566,8 +1572,8 @@ packages:
hasBin: true
dev: true
/update-browserslist-db/1.0.4_browserslist@4.21.2:
resolution: {integrity: sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==}
/update-browserslist-db/1.0.5_browserslist@4.21.2:
resolution: {integrity: sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
@@ -1577,8 +1583,8 @@ packages:
picocolors: 1.0.0
dev: true
/vite/3.0.1:
resolution: {integrity: sha512-nefKSglkoEsDpYUkBuT2++L04ktcP8fz8dxLtmZdDdMyhubFSOLFw6BTh/46Fc6tIX/cibs/NVYWNrsqn0k6pQ==}
/vite/3.0.2:
resolution: {integrity: sha512-TAqydxW/w0U5AoL5AsD9DApTvGb2iNbGs3sN4u2VdT1GFkQVUfgUldt+t08TZgi23uIauh1TUOQJALduo9GXqw==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
peerDependencies:

View File

@@ -14,7 +14,9 @@
--ui-text-0: #fefefe;
--ui-text-1: #eee;
--ui-clr-primary: hsl(186, 65%, 55%);
--ui-clr-primary-alt: hsl(186, 85%, 35%);
--ui-clr-error: hsl(357, 77%, 51%);
--ui-clr-error-alt: hsl(357, 87%, 41%);
--ui-anim: all 150ms ease;
}

37
frontend/src/lib/toast.ts Normal file
View File

@@ -0,0 +1,37 @@
import { toast, type SvelteToastOptions } from '@zerodevx/svelte-toast'
export enum NotifyType {
Success = 'success',
Error = 'error',
}
const themeMapping: Record<NotifyType, SvelteToastOptions['theme']> = {
[NotifyType.Success]: {
'--toastBackground': 'var(--ui-clr-primary)',
'--toastBarBackground': 'var(--ui-clr-primary-alt)',
},
[NotifyType.Error]: {
'--toastBackground': 'var(--ui-clr-error)',
'--toastBarBackground': 'var(--ui-clr-error-alt)',
},
}
function notifyFN(message: string, type: NotifyType = NotifyType.Success) {
const options: SvelteToastOptions = {
duration: 5_000,
theme: {
...themeMapping[type],
'--toastBarHeight': '0.25rem',
'--toastMinHeight': 'auto',
'--toastMsgPadding': '0.5rem',
'--toastBorderRadius': '0',
},
}
toast.push(message, options)
}
export const notify = {
success: (message: string) => notifyFN(message, NotifyType.Success),
error: (message: string) => notifyFN(message, NotifyType.Error),
}

View File

@@ -12,6 +12,7 @@
<div class="fields">
<TextInput
data-testid="field-views"
type="number"
label={$t('common.views', { values: { n: 0 } })}
bind:value={note.views}
@@ -22,9 +23,15 @@
$t('home.errors.max', { values: { n: $status?.max_views ?? 0 } })}
/>
<div class="middle-switch">
<Switch label={$t('common.mode')} bind:value={timeExpiration} color={false} />
<Switch
data-testid="switch-advanced-toggle"
label={$t('common.mode')}
bind:value={timeExpiration}
color={false}
/>
</div>
<TextInput
data-testid="field-expiration"
type="number"
label={$t('common.minutes', { values: { n: 0 } })}
bind:value={note.expiration}

View File

@@ -34,7 +34,7 @@
<small>
{label}
</small>
<input type="file" on:change={onInput} multiple />
<input {...$$restProps} type="file" on:change={onInput} multiple />
<div class="box">
{#if files.length}
<div>

View File

@@ -24,6 +24,7 @@
label={$t('common.share_link')}
value="{window.location.origin}/note/{result.id}#{result.password}"
copy
data-testid="share-link"
/>
<br />
<p>

View File

@@ -3,7 +3,6 @@
</script>
<script lang="ts">
import copy from 'copy-to-clipboard'
import DOMPurify from 'dompurify'
import { saveAs } from 'file-saver'
import prettyBytes from 'pretty-bytes'
@@ -11,6 +10,7 @@
import type { FileDTO, NotePublic } from '$lib/api'
import Button from '$lib/ui/Button.svelte'
import { copy } from '$lib/utils'
export let note: DecryptedNote
@@ -44,20 +44,22 @@
</script>
<p class="error-text">{@html $t('show.warning_will_not_see_again')}</p>
{#if note.meta.type === 'text'}
<div class="note">
{@html contentWithLinks(note.contents)}
</div>
<Button on:click={() => copy(note.contents)}>{$t('common.copy_clipboard')}</Button>
{:else}
{#each files as file}
<div class="note file">
<b on:click={() => downloadFile(file)}> {file.name}</b>
<small> {file.type} {prettyBytes(file.size)}</small>
<div data-testid="result">
{#if note.meta.type === 'text'}
<div class="note">
{@html contentWithLinks(note.contents)}
</div>
{/each}
<Button on:click={download}>{$t('show.download_all')}</Button>
{/if}
<Button on:click={() => copy(note.contents)}>{$t('common.copy_clipboard')}</Button>
{:else}
{#each files as file}
<div class="note file">
<b on:click={() => downloadFile(file)}> {file.name}</b>
<small> {file.type} {prettyBytes(file.size)}</small>
</div>
{/each}
<Button on:click={download}>{$t('show.download_all')}</Button>
{/if}
</div>
<style>
.note {

View File

@@ -1,10 +1,7 @@
<script lang="ts">
import copyToClipboard from 'copy-to-clipboard'
import { t } from 'svelte-intl-precompile'
import { fade } from 'svelte/transition'
import { Crypto, Hex } from '$lib/crypto'
import Icon from '$lib/ui/Icon.svelte'
import { copy as copyFN } from '$lib/utils'
export let label: string = ''
export let value: any
@@ -15,8 +12,6 @@
const initialType = $$restProps.type
const isPassword = initialType === 'password'
let hidden = true
let notification: string | null = null
let notificationTimeout: NodeJS.Timeout | null = null
$: valid = validate(value)
@@ -28,23 +23,9 @@
function toggle() {
hidden = !hidden
}
function copyFN() {
copyToClipboard(value.toString())
notify($t('home.copied_to_clipboard'))
}
function randomFN() {
value = Hex.encode(Crypto.getRandomBytes(20))
}
function notify(msg: string, delay: number = 2000) {
if (notificationTimeout) {
clearTimeout(notificationTimeout)
}
notificationTimeout = setTimeout(() => {
notification = null
}, delay)
notification = msg
}
</script>
<label>
@@ -63,12 +44,9 @@
<Icon class="icon" icon="dice" on:click={randomFN} />
{/if}
{#if copy}
<Icon class="icon" icon="copy" on:click={copyFN} />
<Icon class="icon" icon="copy" on:click={() => copyFN(value.toString())} />
{/if}
</div>
{#if notification}
<div class="notification" transition:fade><small>{notification}</small></div>
{/if}
</label>
<style>
@@ -118,11 +96,4 @@
.icons > :global(.icon:hover) {
border-color: var(--ui-clr-primary);
}
.notification {
text-align: right;
position: absolute;
right: 0;
bottom: -1.5em;
}
</style>

11
frontend/src/lib/utils.ts Normal file
View File

@@ -0,0 +1,11 @@
import copyToClipboard from 'copy-to-clipboard'
import { t } from 'svelte-intl-precompile'
import { get } from 'svelte/store'
import { notify } from './toast'
export function copy(value: string) {
copyToClipboard(value)
const msg = get(t)('common.copied_to_clipboard')
notify.success(msg)
}

View File

@@ -7,6 +7,7 @@
import { create, PayloadToLargeError } from '$lib/api'
import { Crypto, Hex } from '$lib/crypto'
import { status } from '$lib/stores/status'
import { notify } from '$lib/toast'
import AdvancedParameters from '$lib/ui/AdvancedParameters.svelte'
import Button from '$lib/ui/Button.svelte'
import FileUpload from '$lib/ui/FileUpload.svelte'
@@ -29,7 +30,6 @@
let timeExpiration = false
let description = ''
let loading: string | null = null
let error: string | null = null
$: if (!advanced) {
note.views = 1
@@ -56,7 +56,6 @@
async function submit() {
try {
error = null
loading = $t('common.encrypting')
const password = Hex.encode(Crypto.getRandomBytes(32))
@@ -82,14 +81,15 @@
password: password,
id: response.id,
}
notify.success($t('home.messages.note_created'))
} catch (e) {
if (e instanceof PayloadToLargeError) {
error = $t('home.errors.note_to_big')
notify.error($t('home.errors.note_to_big'))
} else if (e instanceof EmptyContentError) {
error = $t('home.errors.empty_content')
notify.error($t('home.errors.empty_content'))
} else {
console.error(e)
error = $t('home.errors.note_error')
notify.error($t('home.errors.note_error'))
}
} finally {
loading = null
@@ -106,15 +106,29 @@
<form on:submit|preventDefault={submit}>
<fieldset disabled={loading !== null}>
{#if isFile}
<FileUpload label={$t('common.file')} bind:files />
<FileUpload data-testid="file-upload" label={$t('common.file')} bind:files />
{:else}
<TextArea label={$t('common.note')} bind:value={note.contents} placeholder="..." />
<TextArea
data-testid="text-field"
label={$t('common.note')}
bind:value={note.contents}
placeholder="..."
/>
{/if}
<div class="bottom">
<Switch class="file" label={$t('common.file')} bind:value={isFile} />
<Switch
data-testid="switch-file"
class="file"
label={$t('common.file')}
bind:value={isFile}
/>
{#if $status?.allow_advanced}
<Switch label={$t('common.advanced')} bind:value={advanced} />
<Switch
data-testid="switch-advanced"
label={$t('common.advanced')}
bind:value={advanced}
/>
{/if}
<div class="grow" />
<div class="tr">
@@ -124,10 +138,6 @@
</div>
</div>
{#if error}
<div class="error-text">{error}</div>
{/if}
<p>
<br />
{#if loading}
@@ -161,8 +171,4 @@
.grow {
flex: 1;
}
.error-text {
margin-top: 0.5rem;
}
</style>

View File

@@ -7,11 +7,14 @@
</script>
<script lang="ts">
import { SvelteToast } from '@zerodevx/svelte-toast'
import { onMount } from 'svelte'
import '../app.css'
import { init as initStores } from '$lib/stores/status'
import Footer from '$lib/views/Footer.svelte'
import Header from '$lib/views/Header.svelte'
import { onMount } from 'svelte'
import '../app.css'
onMount(() => {
initStores()
@@ -28,6 +31,8 @@
<slot />
</main>
<SvelteToast />
<Footer />
{/await}

View File

@@ -86,7 +86,7 @@
<form on:submit|preventDefault={show}>
<fieldset>
<p>{$t('show.explanation')}</p>
<Button type="submit">{$t('show.show_note')}</Button>
<Button data-testid="show-note-button" type="submit">{$t('show.show_note')}</Button>
{#if error}
<br />
<p class="error-text">

View File

@@ -4,9 +4,20 @@
"dev:backend": "cd backend && cargo watch -x 'run --bin cryptgeon'",
"dev:front": "pnpm --prefix frontend run dev",
"dev:proxy": "node proxy.mjs",
"dev": "run-p dev:*"
"dev": "run-p dev:*",
"test": "playwright test --project chrome firefox safari",
"test:local": "playwright test --project local",
"ci:server": "cd backend && SIZE_LIMIT=10MiB LISTEN_ADDR=0.0.0.0:1234 cargo run",
"ci:server:backend": "cd backend && cargo run",
"ci:server:front": "pnpm --prefix frontend run preview",
"ci:server:proxy": "node proxy.mjs",
"ci:prepare": "run-p ci:prepare:*",
"ci:prepare:backend": "cd backend && cargo build",
"ci:prepare:front": "pnpm --prefix frontend install && pnpm --prefix frontend run build"
},
"devDependencies": {
"@playwright/test": "^1.23.4",
"@types/node": "16",
"http-proxy": "^1.18.1",
"npm-run-all": "^4.1.5"
}

30
playwright.config.ts Normal file
View File

@@ -0,0 +1,30 @@
import { devices, type PlaywrightTestConfig } from '@playwright/test'
const config: PlaywrightTestConfig = {
use: {
video: 'retain-on-failure',
baseURL: 'http://localhost:1234',
actionTimeout: 60_000,
},
outputDir: './test-results',
testDir: './test',
webServer: {
command: 'pnpm run ci:server',
port: 1234,
reuseExistingServer: true,
},
projects: [
{ name: 'chrome', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'safari', use: { ...devices['Desktop Safari'] } },
{
name: 'local',
use: { ...devices['Desktop Chrome'] },
// testMatch: 'file/too-big.spec.ts',
},
],
}
export default config

27
pnpm-lock.yaml generated
View File

@@ -1,15 +1,36 @@
lockfileVersion: 5.4
specifiers:
'@playwright/test': ^1.23.4
'@types/node': '16'
http-proxy: ^1.18.1
npm-run-all: ^4.1.5
devDependencies:
'@playwright/test': 1.23.4
'@types/node': 16.11.45
http-proxy: 1.18.1
npm-run-all: 4.1.5
packages:
/@playwright/test/1.23.4:
resolution: {integrity: sha512-iIsoMJDS/lyuhw82FtcV/B3PXikgVD3hNe5hyvOpRM0uRr1OIpN3LgPeRbBjhzBWmyf6RgRg5fqK5sVcpA03yA==}
engines: {node: '>=14'}
hasBin: true
dependencies:
'@types/node': 18.0.6
playwright-core: 1.23.4
dev: true
/@types/node/16.11.45:
resolution: {integrity: sha512-3rKg/L5x0rofKuuUt5zlXzOnKyIHXmIu5R8A0TuNDMF2062/AOIDBciFIjToLEJ/9F9DzkHNot+BpNsMI1OLdQ==}
dev: true
/@types/node/18.0.6:
resolution: {integrity: sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw==}
dev: true
/ansi-styles/3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
engines: {node: '>=4'}
@@ -430,6 +451,12 @@ packages:
engines: {node: '>=4'}
dev: true
/playwright-core/1.23.4:
resolution: {integrity: sha512-h5V2yw7d8xIwotjyNrkLF13nV9RiiZLHdXeHo+nVJIYGVlZ8U2qV0pMxNJKNTvfQVT0N8/A4CW6/4EW2cOcTiA==}
engines: {node: '>=14'}
hasBin: true
dev: true
/read-pkg/3.0.0:
resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==}
engines: {node: '>=4'}

BIN
test/assets/AES.pdf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
test/assets/Pigeons.zip (Stored with Git LFS) Normal file

Binary file not shown.

BIN
test/assets/alfred-kenneally-UIu4RmMxnHU-unsplash.jpg (Stored with Git LFS) Normal file

Binary file not shown.

5
test/file/files.ts Normal file
View File

@@ -0,0 +1,5 @@
export default {
PDF: 'test/assets/AES.pdf',
Image: 'test/assets/alfred-kenneally-UIu4RmMxnHU-unsplash.jpg',
Zip: 'test/assets/Pigeons.zip',
}

View File

@@ -0,0 +1,11 @@
import { test } from '@playwright/test'
import { checkLinkForDownload, createNote, getFileChecksum } from '../utils'
import Files from './files'
test('multiple', async ({ page }) => {
const files = [Files.PDF, Files.Image]
const checksums = await Promise.all(files.map(getFileChecksum))
const link = await createNote(page, { files, views: 2 })
await checkLinkForDownload(page, link, 'alfred-kenneally', checksums[1])
await checkLinkForDownload(page, link, 'AES.pdf', checksums[0])
})

24
test/file/simple.spec.ts Normal file
View File

@@ -0,0 +1,24 @@
import { test } from '@playwright/test'
import { checkLinkDoesNotExist, checkLinkForDownload, checkLinkForText, createNote, getFileChecksum } from '../utils'
import Files from './files'
test('simple pdf', async ({ page }) => {
const files = [Files.PDF]
const link = await createNote(page, { files })
await checkLinkForText(page, link, 'AES.pdf')
await checkLinkDoesNotExist(page, link)
})
test('pdf content', async ({ page }) => {
const files = [Files.PDF]
const checksum = await getFileChecksum(files[0])
const link = await createNote(page, { files })
await checkLinkForDownload(page, link, 'AES.pdf', checksum)
})
test('image content', async ({ page }) => {
const files = [Files.Image]
const checksum = await getFileChecksum(files[0])
const link = await createNote(page, { files })
await checkLinkForDownload(page, link, 'alfred-kenneally', checksum)
})

37
test/text.ts Normal file
View File

@@ -0,0 +1,37 @@
import { expect, test, type Page } from '@playwright/test'
async function createNote(page: Page, text: string): Promise<string> {
await page.goto('/')
await page.locator('textarea').click()
await page.locator('textarea').fill(text)
await page.locator('button:has-text("create")').click()
await page.locator('[data-testid="share-link"]').click()
const shareLink = await page.locator('[data-testid="share-link"]').inputValue()
return shareLink
}
async function checkLinkForText(page: Page, link: string, text: string) {
await page.goto(link)
await page.locator('[data-testid="show-note-button"]').click()
expect(await page.locator('[data-testid="result"] >> .note').innerText()).toBe(text)
}
async function checkLinkDoesNotExist(page: Page, link: string) {
await page.goto('/') // Required due to firefox: https://github.com/microsoft/playwright/issues/15781
await page.goto(link)
await expect(page.locator('main')).toContainText('note was not found or was already deleted')
}
test('simple', async ({ page }) => {
const text = `Endless prejudice endless play derive joy eternal-return selfish burying. Of decieve play pinnacle faith disgust. Spirit reason salvation burying strong of joy ascetic selfish against merciful sea truth. Ubermensch moral prejudice derive chaos mountains ubermensch justice philosophy justice ultimate joy ultimate transvaluation. Virtues convictions war ascetic eternal-return spirit. Ubermensch transvaluation noble revaluation sexuality intentions salvation endless decrepit hope noble fearful. Justice ideal ultimate snare god joy evil sexuality insofar gains oneself ideal.`
const shareLink = await createNote(page, text)
await checkLinkForText(page, shareLink, text)
})
test('only shown once', async ({ page }) => {
const text = `Christian victorious reason suicide dead. Right ultimate gains god hope truth burying selfish society joy. Ultimate.`
const shareLink = await createNote(page, text)
await checkLinkForText(page, shareLink, text)
await checkLinkDoesNotExist(page, shareLink)
})

View File

@@ -0,0 +1,14 @@
import { test } from '@playwright/test'
import { checkLinkDoesNotExist, checkLinkForText, createNote } from '../utils'
test('1 minute', async ({ page }) => {
const text = `Virtues value ascetic revaluation sea dead strong burying.`
const minutes = 1
const timeout = minutes * 60_000
test.setTimeout(timeout * 2)
const shareLink = await createNote(page, { text, expiration: minutes })
await checkLinkForText(page, shareLink, text)
await checkLinkForText(page, shareLink, text)
await page.waitForTimeout(timeout)
await checkLinkDoesNotExist(page, shareLink)
})

8
test/text/simple.spec.ts Normal file
View File

@@ -0,0 +1,8 @@
import { test } from '@playwright/test'
import { checkLinkForText, createNote } from '../utils'
test('simple', async ({ page }) => {
const text = `Endless prejudice endless play derive joy eternal-return selfish burying. Of decieve play pinnacle faith disgust. Spirit reason salvation burying strong of joy ascetic selfish against merciful sea truth. Ubermensch moral prejudice derive chaos mountains ubermensch justice philosophy justice ultimate joy ultimate transvaluation. Virtues convictions war ascetic eternal-return spirit. Ubermensch transvaluation noble revaluation sexuality intentions salvation endless decrepit hope noble fearful. Justice ideal ultimate snare god joy evil sexuality insofar gains oneself ideal.`
const shareLink = await createNote(page, { text })
await checkLinkForText(page, shareLink, text)
})

18
test/text/views.spec.ts Normal file
View File

@@ -0,0 +1,18 @@
import { test } from '@playwright/test'
import { checkLinkDoesNotExist, checkLinkForText, createNote } from '../utils'
test('only shown once', async ({ page }) => {
const text = `Christian victorious reason suicide dead. Right ultimate gains god hope truth burying selfish society joy. Ultimate.`
const shareLink = await createNote(page, { text })
await checkLinkForText(page, shareLink, text)
await checkLinkDoesNotExist(page, shareLink)
})
test('view 3 times', async ({ page }) => {
const text = `Justice holiest overcome fearful strong ultimate holiest christianity.`
const shareLink = await createNote(page, { text, views: 3 })
await checkLinkForText(page, shareLink, text)
await checkLinkForText(page, shareLink, text)
await checkLinkForText(page, shareLink, text)
await checkLinkDoesNotExist(page, shareLink)
})

71
test/utils.ts Normal file
View File

@@ -0,0 +1,71 @@
import { expect, type Page } from '@playwright/test'
import { createHash } from 'crypto'
import { readFile } from 'fs/promises'
type CreatePage = { text?: string; files?: string[]; views?: number; expiration?: number; error?: string }
export async function createNote(page: Page, options: CreatePage): Promise<string> {
await page.goto('/')
if (options.text) {
await page.locator('[data-testid="text-field"]').fill(options.text)
} else if (options.files) {
await page.locator('[data-testid="switch-file"]').click()
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page.locator('text=No Files Selected').click(),
])
await fileChooser.setFiles(options.files)
}
if (options.views) {
await page.locator('[data-testid="switch-advanced"]').click()
await page.locator('[data-testid="field-views"]').fill(options.views.toString())
} else if (options.expiration) {
await page.locator('[data-testid="switch-advanced"]').click()
await page.locator('[data-testid="switch-advanced-toggle"]').click()
await page.locator('[data-testid="field-expiration"]').fill(options.expiration.toString())
}
await page.locator('button:has-text("create")').click()
if (options.error) {
await expect(page.locator('.error-text')).toContainText(options.error, { timeout: 60_000 })
}
const shareLink = await page.locator('[data-testid="share-link"]').inputValue()
return shareLink
}
export async function checkLinkForDownload(page: Page, link: string, text: string, checksum: string) {
await page.goto('/')
await page.goto(link)
await page.locator('[data-testid="show-note-button"]').click()
const [download] = await Promise.all([
page.waitForEvent('download'),
page.locator(`[data-testid="result"] >> text=${text}`).click(),
])
const path = await download.path()
if (!path) throw new Error('Download failed')
const cs = await getFileChecksum(path)
await expect(cs).toBe(checksum)
}
export async function checkLinkForText(page: Page, link: string, text: string) {
await page.goto('/')
await page.goto(link)
await page.locator('[data-testid="show-note-button"]').click()
await expect(await page.locator('[data-testid="result"] >> .note').innerText()).toContain(text)
}
export async function checkLinkDoesNotExist(page: Page, link: string) {
await page.goto('/') // Required due to firefox: https://github.com/microsoft/playwright/issues/15781
await page.goto(link)
await expect(page.locator('main')).toContainText('note was not found or was already deleted')
}
export async function getFileChecksum(file: string) {
const buffer = await readFile(file)
const hash = createHash('sha3-256').update(buffer).digest('hex')
return hash
}