diff --git a/.travis.yml b/.travis.yml index 6609ead81..3e9dfaf35 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,110 +5,256 @@ dist: trusty language: node_js node_js: - '8' +env: + global: + - NPM_EMAIL="scion.collaborator@gmail.com" + - NPM_TOKEN="FbNdERSRydcm38qnM9XV2HRSYhh7yi33JJe5FUSITDwQh8PAHjmxC5yCvyHHg2WBq5bLBS54oeR8NwomcTQXFL0s8d4Pte32sHBVgNnZ34U1IJDQ6Eufy01dZZjtZNfyv+3CeWOKgHWB963aBbCnfTisN2C66FBLQ4cK1cUO7UaMPZ6ux1wjhtFtd/Isih4TYNCmMRYGYZO1cNO+Ua84dm0j3ko8Hwx68Er3Ce3isQKqHKtXA5d0bX4/bYZuiXwWZwM/QQ2YMCALy8XOSBoE8ObBqOUiutO9LCsGEcPP5LiuWp0D1uqSvajdRhlmoTL3TI3/qDoLtkphww70JYzgRI8HJRekzGvg/gNqJrCDDxz51LpHVrH95TFS+pb9yw/OeEnT6HTpk3C0wc1KUx04EP90bJg36do+eixir3bqQsVJnFMZaBCPGOEyNHsHEqJ5tyKtqVW7UQeyynZGpNXhK1QRS+SQmisLP8nlo6QFheSn965Ke7kYoGB2olasUo7YKJko70bK4kCJOCPdbFyRQUBWGxjdFaAk29a9ekSj6RrkhazjqlxtZAUK72FSlj8UzloJKLugsBfhWEGPqorprbhyeK3qZUUj3ammiu4VW057A2FASAsE9Y80yzrO+0dJeIsTjHC6zNIaQj3KxR7jJBLGe0pKMpql75Adne+ThZM=" + - secure: "W2MbFhPYKn4nBzW9uV5GlRBU7Bsml3CA2sGapskwHouAW/5KApc8OPirhlu37LdUKaPmJwacWOugK9SPU5Fh2kBEad4ktlyVpbwIbzt3R29EpdolnaeUX2cpGfqUWxSN3EA02mlKyExfTQL7AYJlrysgVc+5D1FyvZQ/Ej4sw8NvbUbuRxUXiOCP9FpZ3GYa3Tprl3cfQpgHPUqxO64kvym1kZZAYXkrLiidGA5riiWyY8b4waBv7cg+tpYzsqkPCSdlnTaisy1Lo5vMaAXsKroWnJHRH6ONPJr8qw1YmffSslpXYeENDKJQNKPdd9i5M56Vq7sV2jHb9vWP8CdOUxO9298pC6ymQ28EJFttEG/0CrLCZdkDbf9HcKy6ZygOyrow/FDOaOCJSilNzecfeNJmF5Av+tt7tg4zSLVK33mKFJSrrPvalG44iIHIgHHWd8LOGAy7k4pzquW1zbTVMqcqctYxUWYTy8s2r/7Nxy/f0bdb5XHwj91TOnp8+mSR1FI8UGgvmc8Y8BuXSwz9cdihX6nM7u+gVpEwgRpelej05dathtmUtuSTAZBdETBspFrCCjzSy7J/9M5/nVXArf875qzqWfj4/KNqqAeg/YNWdI2wlW90JQgt5msOe1awwuWttFVyO3mdT6gvkaPrkpxbDW/yHgr8pXiGIHm1iAE=" # $NOW_TOKEN install: - npm install -script: -- npm run build jobs: include: - - stage: lint and test all packages + - stage: "Lint" + name: "lint Workbench & Workbench Application Platform" + script: + - npm run lint + - + name: "lint E2E-App (Workbench Application Platform)" + script: + - npm run e2e:workbench-application-platform:lint + - stage: "Build, Test, Deploy, Publish" + name: "build, test, deploy E2E-App (if branch=master) to 'https://zeit.co/scion', publish Workbench & Workbench Application Platform to NPM (if branch=master AND if release tag)" script: - npm run build - - npm run lint - npm run test - - stage: release mouse-dispatcher package - if: tag IS present - before_deploy: - - cd dist/scion/mouse-dispatcher - after_deploy: - - cd ../../.. - deploy: - - provider: npm - email: scion.collaborator@gmail.com - api_key: - secure: "FbNdERSRydcm38qnM9XV2HRSYhh7yi33JJe5FUSITDwQh8PAHjmxC5yCvyHHg2WBq5bLBS54oeR8NwomcTQXFL0s8d4Pte32sHBVgNnZ34U1IJDQ6Eufy01dZZjtZNfyv+3CeWOKgHWB963aBbCnfTisN2C66FBLQ4cK1cUO7UaMPZ6ux1wjhtFtd/Isih4TYNCmMRYGYZO1cNO+Ua84dm0j3ko8Hwx68Er3Ce3isQKqHKtXA5d0bX4/bYZuiXwWZwM/QQ2YMCALy8XOSBoE8ObBqOUiutO9LCsGEcPP5LiuWp0D1uqSvajdRhlmoTL3TI3/qDoLtkphww70JYzgRI8HJRekzGvg/gNqJrCDDxz51LpHVrH95TFS+pb9yw/OeEnT6HTpk3C0wc1KUx04EP90bJg36do+eixir3bqQsVJnFMZaBCPGOEyNHsHEqJ5tyKtqVW7UQeyynZGpNXhK1QRS+SQmisLP8nlo6QFheSn965Ke7kYoGB2olasUo7YKJko70bK4kCJOCPdbFyRQUBWGxjdFaAk29a9ekSj6RrkhazjqlxtZAUK72FSlj8UzloJKLugsBfhWEGPqorprbhyeK3qZUUj3ammiu4VW057A2FASAsE9Y80yzrO+0dJeIsTjHC6zNIaQj3KxR7jJBLGe0pKMpql75Adne+ThZM=" - skip_cleanup: true - on: - tags: true - condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+$" # Matches 'stable' versions x.x.x (npm tag: latest) - - provider: npm - tag: next - email: scion.collaborator@gmail.com - api_key: - secure: "FbNdERSRydcm38qnM9XV2HRSYhh7yi33JJe5FUSITDwQh8PAHjmxC5yCvyHHg2WBq5bLBS54oeR8NwomcTQXFL0s8d4Pte32sHBVgNnZ34U1IJDQ6Eufy01dZZjtZNfyv+3CeWOKgHWB963aBbCnfTisN2C66FBLQ4cK1cUO7UaMPZ6ux1wjhtFtd/Isih4TYNCmMRYGYZO1cNO+Ua84dm0j3ko8Hwx68Er3Ce3isQKqHKtXA5d0bX4/bYZuiXwWZwM/QQ2YMCALy8XOSBoE8ObBqOUiutO9LCsGEcPP5LiuWp0D1uqSvajdRhlmoTL3TI3/qDoLtkphww70JYzgRI8HJRekzGvg/gNqJrCDDxz51LpHVrH95TFS+pb9yw/OeEnT6HTpk3C0wc1KUx04EP90bJg36do+eixir3bqQsVJnFMZaBCPGOEyNHsHEqJ5tyKtqVW7UQeyynZGpNXhK1QRS+SQmisLP8nlo6QFheSn965Ke7kYoGB2olasUo7YKJko70bK4kCJOCPdbFyRQUBWGxjdFaAk29a9ekSj6RrkhazjqlxtZAUK72FSlj8UzloJKLugsBfhWEGPqorprbhyeK3qZUUj3ammiu4VW057A2FASAsE9Y80yzrO+0dJeIsTjHC6zNIaQj3KxR7jJBLGe0pKMpql75Adne+ThZM=" - skip_cleanup: true - on: - tags: true - condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+-" # Matches 'next' versions x.x.x-beta and x.x.x-rc (npm tag: next) - - stage: release dimension package - if: tag IS present - before_deploy: - - cd dist/scion/dimension - after_deploy: - - cd ../../.. - deploy: - - provider: npm - email: scion.collaborator@gmail.com - api_key: - secure: "FbNdERSRydcm38qnM9XV2HRSYhh7yi33JJe5FUSITDwQh8PAHjmxC5yCvyHHg2WBq5bLBS54oeR8NwomcTQXFL0s8d4Pte32sHBVgNnZ34U1IJDQ6Eufy01dZZjtZNfyv+3CeWOKgHWB963aBbCnfTisN2C66FBLQ4cK1cUO7UaMPZ6ux1wjhtFtd/Isih4TYNCmMRYGYZO1cNO+Ua84dm0j3ko8Hwx68Er3Ce3isQKqHKtXA5d0bX4/bYZuiXwWZwM/QQ2YMCALy8XOSBoE8ObBqOUiutO9LCsGEcPP5LiuWp0D1uqSvajdRhlmoTL3TI3/qDoLtkphww70JYzgRI8HJRekzGvg/gNqJrCDDxz51LpHVrH95TFS+pb9yw/OeEnT6HTpk3C0wc1KUx04EP90bJg36do+eixir3bqQsVJnFMZaBCPGOEyNHsHEqJ5tyKtqVW7UQeyynZGpNXhK1QRS+SQmisLP8nlo6QFheSn965Ke7kYoGB2olasUo7YKJko70bK4kCJOCPdbFyRQUBWGxjdFaAk29a9ekSj6RrkhazjqlxtZAUK72FSlj8UzloJKLugsBfhWEGPqorprbhyeK3qZUUj3ammiu4VW057A2FASAsE9Y80yzrO+0dJeIsTjHC6zNIaQj3KxR7jJBLGe0pKMpql75Adne+ThZM=" - skip_cleanup: true - on: - tags: true - condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+$" # Matches 'stable' versions x.x.x (npm tag: latest) - - provider: npm - tag: next - email: scion.collaborator@gmail.com - api_key: - secure: "FbNdERSRydcm38qnM9XV2HRSYhh7yi33JJe5FUSITDwQh8PAHjmxC5yCvyHHg2WBq5bLBS54oeR8NwomcTQXFL0s8d4Pte32sHBVgNnZ34U1IJDQ6Eufy01dZZjtZNfyv+3CeWOKgHWB963aBbCnfTisN2C66FBLQ4cK1cUO7UaMPZ6ux1wjhtFtd/Isih4TYNCmMRYGYZO1cNO+Ua84dm0j3ko8Hwx68Er3Ce3isQKqHKtXA5d0bX4/bYZuiXwWZwM/QQ2YMCALy8XOSBoE8ObBqOUiutO9LCsGEcPP5LiuWp0D1uqSvajdRhlmoTL3TI3/qDoLtkphww70JYzgRI8HJRekzGvg/gNqJrCDDxz51LpHVrH95TFS+pb9yw/OeEnT6HTpk3C0wc1KUx04EP90bJg36do+eixir3bqQsVJnFMZaBCPGOEyNHsHEqJ5tyKtqVW7UQeyynZGpNXhK1QRS+SQmisLP8nlo6QFheSn965Ke7kYoGB2olasUo7YKJko70bK4kCJOCPdbFyRQUBWGxjdFaAk29a9ekSj6RrkhazjqlxtZAUK72FSlj8UzloJKLugsBfhWEGPqorprbhyeK3qZUUj3ammiu4VW057A2FASAsE9Y80yzrO+0dJeIsTjHC6zNIaQj3KxR7jJBLGe0pKMpql75Adne+ThZM=" - skip_cleanup: true - on: - tags: true - condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+-" # Matches 'next' versions x.x.x-beta and x.x.x-rc (npm tag: next) - - stage: release viewport package - if: tag IS present - before_deploy: - - cd dist/scion/viewport - after_deploy: - - cd ../../.. - deploy: - - provider: npm - email: scion.collaborator@gmail.com - api_key: - secure: "FbNdERSRydcm38qnM9XV2HRSYhh7yi33JJe5FUSITDwQh8PAHjmxC5yCvyHHg2WBq5bLBS54oeR8NwomcTQXFL0s8d4Pte32sHBVgNnZ34U1IJDQ6Eufy01dZZjtZNfyv+3CeWOKgHWB963aBbCnfTisN2C66FBLQ4cK1cUO7UaMPZ6ux1wjhtFtd/Isih4TYNCmMRYGYZO1cNO+Ua84dm0j3ko8Hwx68Er3Ce3isQKqHKtXA5d0bX4/bYZuiXwWZwM/QQ2YMCALy8XOSBoE8ObBqOUiutO9LCsGEcPP5LiuWp0D1uqSvajdRhlmoTL3TI3/qDoLtkphww70JYzgRI8HJRekzGvg/gNqJrCDDxz51LpHVrH95TFS+pb9yw/OeEnT6HTpk3C0wc1KUx04EP90bJg36do+eixir3bqQsVJnFMZaBCPGOEyNHsHEqJ5tyKtqVW7UQeyynZGpNXhK1QRS+SQmisLP8nlo6QFheSn965Ke7kYoGB2olasUo7YKJko70bK4kCJOCPdbFyRQUBWGxjdFaAk29a9ekSj6RrkhazjqlxtZAUK72FSlj8UzloJKLugsBfhWEGPqorprbhyeK3qZUUj3ammiu4VW057A2FASAsE9Y80yzrO+0dJeIsTjHC6zNIaQj3KxR7jJBLGe0pKMpql75Adne+ThZM=" - skip_cleanup: true - on: - tags: true - condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+$" # Matches 'stable' versions x.x.x (npm tag: latest) - - provider: npm - tag: next - email: scion.collaborator@gmail.com - api_key: - secure: "FbNdERSRydcm38qnM9XV2HRSYhh7yi33JJe5FUSITDwQh8PAHjmxC5yCvyHHg2WBq5bLBS54oeR8NwomcTQXFL0s8d4Pte32sHBVgNnZ34U1IJDQ6Eufy01dZZjtZNfyv+3CeWOKgHWB963aBbCnfTisN2C66FBLQ4cK1cUO7UaMPZ6ux1wjhtFtd/Isih4TYNCmMRYGYZO1cNO+Ua84dm0j3ko8Hwx68Er3Ce3isQKqHKtXA5d0bX4/bYZuiXwWZwM/QQ2YMCALy8XOSBoE8ObBqOUiutO9LCsGEcPP5LiuWp0D1uqSvajdRhlmoTL3TI3/qDoLtkphww70JYzgRI8HJRekzGvg/gNqJrCDDxz51LpHVrH95TFS+pb9yw/OeEnT6HTpk3C0wc1KUx04EP90bJg36do+eixir3bqQsVJnFMZaBCPGOEyNHsHEqJ5tyKtqVW7UQeyynZGpNXhK1QRS+SQmisLP8nlo6QFheSn965Ke7kYoGB2olasUo7YKJko70bK4kCJOCPdbFyRQUBWGxjdFaAk29a9ekSj6RrkhazjqlxtZAUK72FSlj8UzloJKLugsBfhWEGPqorprbhyeK3qZUUj3ammiu4VW057A2FASAsE9Y80yzrO+0dJeIsTjHC6zNIaQj3KxR7jJBLGe0pKMpql75Adne+ThZM=" - skip_cleanup: true - on: - tags: true - condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+-" # Matches 'next' versions x.x.x-beta and x.x.x-rc (npm tag: next) - - stage: release workbench package - if: tag IS present - before_deploy: - - cd dist/scion/workbench - after_deploy: - - cd ../../.. + - npm run e2e:workbench-application-platform:build-now + - npm run e2e:workbench-application-platform:e2e deploy: - - provider: npm - email: scion.collaborator@gmail.com - api_key: - secure: "FbNdERSRydcm38qnM9XV2HRSYhh7yi33JJe5FUSITDwQh8PAHjmxC5yCvyHHg2WBq5bLBS54oeR8NwomcTQXFL0s8d4Pte32sHBVgNnZ34U1IJDQ6Eufy01dZZjtZNfyv+3CeWOKgHWB963aBbCnfTisN2C66FBLQ4cK1cUO7UaMPZ6ux1wjhtFtd/Isih4TYNCmMRYGYZO1cNO+Ua84dm0j3ko8Hwx68Er3Ce3isQKqHKtXA5d0bX4/bYZuiXwWZwM/QQ2YMCALy8XOSBoE8ObBqOUiutO9LCsGEcPP5LiuWp0D1uqSvajdRhlmoTL3TI3/qDoLtkphww70JYzgRI8HJRekzGvg/gNqJrCDDxz51LpHVrH95TFS+pb9yw/OeEnT6HTpk3C0wc1KUx04EP90bJg36do+eixir3bqQsVJnFMZaBCPGOEyNHsHEqJ5tyKtqVW7UQeyynZGpNXhK1QRS+SQmisLP8nlo6QFheSn965Ke7kYoGB2olasUo7YKJko70bK4kCJOCPdbFyRQUBWGxjdFaAk29a9ekSj6RrkhazjqlxtZAUK72FSlj8UzloJKLugsBfhWEGPqorprbhyeK3qZUUj3ammiu4VW057A2FASAsE9Y80yzrO+0dJeIsTjHC6zNIaQj3KxR7jJBLGe0pKMpql75Adne+ThZM=" - skip_cleanup: true - on: - tags: true - condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+$" # Matches 'stable' versions x.x.x (npm tag: latest) - - provider: npm - tag: next - email: scion.collaborator@gmail.com - api_key: - secure: "FbNdERSRydcm38qnM9XV2HRSYhh7yi33JJe5FUSITDwQh8PAHjmxC5yCvyHHg2WBq5bLBS54oeR8NwomcTQXFL0s8d4Pte32sHBVgNnZ34U1IJDQ6Eufy01dZZjtZNfyv+3CeWOKgHWB963aBbCnfTisN2C66FBLQ4cK1cUO7UaMPZ6ux1wjhtFtd/Isih4TYNCmMRYGYZO1cNO+Ua84dm0j3ko8Hwx68Er3Ce3isQKqHKtXA5d0bX4/bYZuiXwWZwM/QQ2YMCALy8XOSBoE8ObBqOUiutO9LCsGEcPP5LiuWp0D1uqSvajdRhlmoTL3TI3/qDoLtkphww70JYzgRI8HJRekzGvg/gNqJrCDDxz51LpHVrH95TFS+pb9yw/OeEnT6HTpk3C0wc1KUx04EP90bJg36do+eixir3bqQsVJnFMZaBCPGOEyNHsHEqJ5tyKtqVW7UQeyynZGpNXhK1QRS+SQmisLP8nlo6QFheSn965Ke7kYoGB2olasUo7YKJko70bK4kCJOCPdbFyRQUBWGxjdFaAk29a9ekSj6RrkhazjqlxtZAUK72FSlj8UzloJKLugsBfhWEGPqorprbhyeK3qZUUj3ammiu4VW057A2FASAsE9Y80yzrO+0dJeIsTjHC6zNIaQj3KxR7jJBLGe0pKMpql75Adne+ThZM=" - skip_cleanup: true - on: - tags: true - condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+-" # Matches 'next' versions x.x.x-beta and x.x.x-rc (npm tag: next) + - provider: script + script: bash ./travis-scripts/deploy-now.sh $TRAVIS_BUILD_DIR dist/e2e/workbench-application-platform/host-app $NOW_TOKEN + skip_cleanup: true + on: + branch: workbench-application-platform + - provider: script + script: bash ./travis-scripts/deploy-now.sh $TRAVIS_BUILD_DIR dist/e2e/workbench-application-platform/contact-app $NOW_TOKEN + skip_cleanup: true + on: + branch: workbench-application-platform + - provider: script + script: bash ./travis-scripts/deploy-now.sh $TRAVIS_BUILD_DIR dist/e2e/workbench-application-platform/communication-app $NOW_TOKEN + skip_cleanup: true + on: + branch: workbench-application-platform + - provider: script + script: bash ./travis-scripts/deploy-now.sh $TRAVIS_BUILD_DIR dist/e2e/workbench-application-platform/dev-tools-app $NOW_TOKEN + skip_cleanup: true + on: + branch: workbench-application-platform + # Publish @scion/mouse-dispatcher + - provider: script + script: bash ./travis-scripts/cd.sh $TRAVIS_BUILD_DIR dist/scion/mouse-dispatcher + skip_cleanup: true + on: + branch: master + tags: true + - provider: npm + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+$" # Matches 'stable' versions x.x.x (npm tag: latest) + - provider: npm + tag: next + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+-" # Matches 'next' versions x.x.x-beta and x.x.x-rc (npm tag: next) + # Publish @scion/dimension + - provider: script + script: bash ./travis-scripts/cd.sh $TRAVIS_BUILD_DIR dist/scion/dimension + skip_cleanup: true + on: + branch: master + tags: true + - provider: npm + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+$" # Matches 'stable' versions x.x.x (npm tag: latest) + - provider: npm + tag: next + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+-" # Matches 'next' versions x.x.x-beta and x.x.x-rc (npm tag: next) + # Publish @scion/viewport + - provider: script + script: bash ./travis-scripts/cd.sh $TRAVIS_BUILD_DIR dist/scion/viewport + skip_cleanup: true + on: + branch: master + tags: true + - provider: npm + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+$" # Matches 'stable' versions x.x.x (npm tag: latest) + - provider: npm + tag: next + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+-" # Matches 'next' versions x.x.x-beta and x.x.x-rc (npm tag: next) + # Publish @scion/workbench + - provider: script + script: bash ./travis-scripts/cd.sh $TRAVIS_BUILD_DIR dist/scion/workbench + skip_cleanup: true + on: + branch: master + tags: true + - provider: npm + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+$" # Matches 'stable' versions x.x.x (npm tag: latest) + - provider: npm + tag: next + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+-" # Matches 'next' versions x.x.x-beta and x.x.x-rc (npm tag: next) + # Publish @scion/workbench-application-platform.api + - provider: script + script: bash ./travis-scripts/cd.sh $TRAVIS_BUILD_DIR dist/scion/workbench-application-platform.api + skip_cleanup: true + on: + branch: master + tags: true + - provider: npm + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+$" # Matches 'stable' versions x.x.x (npm tag: latest) + - provider: npm + tag: next + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+-" # Matches 'next' versions x.x.x-beta and x.x.x-rc (npm tag: next) + # Publish @scion/workbench-application-platform + - provider: script + script: bash ./travis-scripts/cd.sh $TRAVIS_BUILD_DIR dist/scion/workbench-application-platform + skip_cleanup: true + on: + branch: master + tags: true + - provider: npm + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+$" # Matches 'stable' versions x.x.x (npm tag: latest) + - provider: npm + tag: next + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+-" # Matches 'next' versions x.x.x-beta and x.x.x-rc (npm tag: next) + # Publish @scion/workbench-application.core + - provider: script + script: bash ./travis-scripts/cd.sh $TRAVIS_BUILD_DIR dist/scion/workbench-application.core + skip_cleanup: true + on: + branch: master + tags: true + - provider: npm + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+$" # Matches 'stable' versions x.x.x (npm tag: latest) + - provider: npm + tag: next + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+-" # Matches 'next' versions x.x.x-beta and x.x.x-rc (npm tag: next) + # Publish @scion/workbench-application.angular + - provider: script + script: bash ./travis-scripts/cd.sh $TRAVIS_BUILD_DIR dist/scion/workbench-application.angular + skip_cleanup: true + on: + branch: master + tags: true + - provider: npm + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+$" # Matches 'stable' versions x.x.x (npm tag: latest) + - provider: npm + tag: next + email: $NPM_EMAIL + api_key: + secure: $NPM_TOKEN + skip_cleanup: true + on: + branch: master + tags: true + condition: "$TRAVIS_TAG =~ ^[0-9]+.[0-9]+.[0-9]+-" # Matches 'next' versions x.x.x-beta and x.x.x-rc (npm tag: next) diff --git a/README.md b/README.md index 5137d709d..1301ff1ac 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,14 @@ # Overview -SCION Workbench helps to build multi-view web applications and integrates separate micro frontends into a consistent rich web application. Views are shown within tabs which can be flexible arranged and dragged around by the user. +SCION Workbench helps to build multi-view web applications and integrates separate micro frontends into a consistent rich web application. Views are shown within tabs which can be flexibly arranged and dragged around by the user. ![SCION Workbench](/resources/site/pics/workbench-small.png) The Workbench provides core features of a modern rich web application. - Tabbed, movable and stackable views - Activity panel as application entry point +- Popups - Global notifications - Global or view-local message boxes - URL encoded navigational state diff --git a/angular.json b/angular.json index 54f657367..8263147d9 100644 --- a/angular.json +++ b/angular.json @@ -16,8 +16,7 @@ "project": "projects/scion/workbench/ng-package.json" }, "configurations": { - "production": { - } + "production": {} } }, "test": { @@ -42,6 +41,149 @@ } } }, + "@scion/workbench-application-platform": { + "root": "projects/scion/workbench-application-platform", + "sourceRoot": "projects/scion/workbench-application-platform/src", + "projectType": "library", + "prefix": "wap", + "architect": { + "build": { + "builder": "@angular-devkit/build-ng-packagr:build", + "options": { + "tsConfig": "projects/scion/workbench-application-platform/tsconfig.lib.json", + "project": "projects/scion/workbench-application-platform/ng-package.json" + }, + "configurations": { + "production": {} + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "projects/scion/workbench-application-platform/src/test.ts", + "tsConfig": "projects/scion/workbench-application-platform/tsconfig.spec.json", + "karmaConfig": "projects/scion/workbench-application-platform/karma.conf.js" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/scion/workbench-application-platform/tsconfig.lib.json", + "projects/scion/workbench-application-platform/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "@scion/workbench-application-platform.api": { + "root": "projects/scion/workbench-application-platform.api", + "sourceRoot": "projects/scion/workbench-application-platform.api/src", + "projectType": "library", + "prefix": "", + "architect": { + "build": { + "builder": "@angular-devkit/build-ng-packagr:build", + "options": { + "tsConfig": "projects/scion/workbench-application-platform.api/tsconfig.lib.json", + "project": "projects/scion/workbench-application-platform.api/ng-package.json" + }, + "configurations": { + "production": {} + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/scion/workbench-application-platform.api/tsconfig.lib.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "@scion/workbench-application.core": { + "root": "projects/scion/workbench-application.core", + "sourceRoot": "projects/scion/workbench-application.core/src", + "projectType": "library", + "prefix": "", + "architect": { + "build": { + "builder": "@angular-devkit/build-ng-packagr:build", + "options": { + "tsConfig": "projects/scion/workbench-application.core/tsconfig.lib.json", + "project": "projects/scion/workbench-application.core/ng-package.json" + }, + "configurations": { + "production": {} + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "projects/scion/workbench-application.core/src/test.ts", + "tsConfig": "projects/scion/workbench-application.core/tsconfig.spec.json", + "karmaConfig": "projects/scion/workbench-application.core/karma.conf.js" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/scion/workbench-application.core/tsconfig.lib.json", + "projects/scion/workbench-application.core/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "@scion/workbench-application.angular": { + "root": "projects/scion/workbench-application.angular", + "sourceRoot": "projects/scion/workbench-application.angular/src", + "projectType": "library", + "prefix": "wb", + "architect": { + "build": { + "builder": "@angular-devkit/build-ng-packagr:build", + "options": { + "tsConfig": "projects/scion/workbench-application.angular/tsconfig.lib.json", + "project": "projects/scion/workbench-application.angular/ng-package.json" + }, + "configurations": { + "production": {} + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "projects/scion/workbench-application.angular/src/test.ts", + "tsConfig": "projects/scion/workbench-application.angular/tsconfig.spec.json", + "karmaConfig": "projects/scion/workbench-application.angular/karma.conf.js" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/scion/workbench-application.angular/tsconfig.lib.json", + "projects/scion/workbench-application.angular/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, "@scion/mouse-dispatcher": { "root": "projects/scion/mouse-dispatcher", "sourceRoot": "projects/scion/mouse-dispatcher/src", @@ -55,8 +197,7 @@ "project": "projects/scion/mouse-dispatcher/ng-package.json" }, "configurations": { - "production": { - } + "production": {} } }, "test": { @@ -156,6 +297,446 @@ } } } + }, + "e2e/workbench-application-platform/test-runner": { + "root": "projects/e2e/workbench-application-platform/test-runner/", + "projectType": "application", + "prefix": "", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "projects/e2e/workbench-application-platform/test-runner/protractor.conf.js" + }, + "configurations": { + "now": { + } + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": "projects/e2e/workbench-application-platform/test-runner/tsconfig.e2e.json", + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "e2e/workbench-application-platform/host-app": { + "root": "projects/e2e/workbench-application-platform/host-app", + "sourceRoot": "projects/e2e/workbench-application-platform/host-app/src", + "projectType": "application", + "prefix": "app", + "schematics": { + "@schematics/angular:component": { + "styleext": "scss" + } + }, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/e2e/workbench-application-platform/host-app", + "index": "projects/e2e/workbench-application-platform/host-app/src/index.html", + "main": "projects/e2e/workbench-application-platform/host-app/src/main.ts", + "polyfills": "projects/e2e/workbench-application-platform/host-app/src/polyfills.ts", + "tsConfig": "projects/e2e/workbench-application-platform/host-app/tsconfig.app.json", + "assets": [ + "projects/e2e/workbench-application-platform/host-app/src/assets", + "projects/e2e/workbench-application-platform/host-app/src/now.json", + "projects/e2e/workbench-application-platform/host-app/src/favicon.ico" + ], + "styles": [ + "projects/e2e/workbench-application-platform/host-app/src/styles.scss" + ], + "stylePreprocessorOptions": { + "includePaths": [ + "projects/e2e/workbench-application-platform/common/src/lib" + ] + }, + "scripts": [] + }, + "configurations": { + "now": { + "fileReplacements": [ + { + "replace": "projects/e2e/workbench-application-platform/host-app/src/environments/environment.ts", + "with": "projects/e2e/workbench-application-platform/host-app/src/environments/environment.now.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "e2e/workbench-application-platform/host-app:build" + }, + "configurations": { + "now": { + "browserTarget": "e2e/workbench-application-platform/host-app:build:now" + } + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/e2e/workbench-application-platform/host-app/tsconfig.app.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "e2e/workbench-application-platform/contact-app": { + "root": "projects/e2e/workbench-application-platform/contact-app", + "sourceRoot": "projects/e2e/workbench-application-platform/contact-app/src", + "projectType": "application", + "prefix": "app", + "schematics": { + "@schematics/angular:component": { + "styleext": "scss" + } + }, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/e2e/workbench-application-platform/contact-app", + "index": "projects/e2e/workbench-application-platform/contact-app/src/index.html", + "main": "projects/e2e/workbench-application-platform/contact-app/src/main.ts", + "polyfills": "projects/e2e/workbench-application-platform/contact-app/src/polyfills.ts", + "tsConfig": "projects/e2e/workbench-application-platform/contact-app/tsconfig.app.json", + "assets": [ + "projects/e2e/workbench-application-platform/contact-app/src/assets", + "projects/e2e/workbench-application-platform/contact-app/src/now.json" + ], + "styles": [ + "projects/e2e/workbench-application-platform/contact-app/src/styles.scss" + ], + "stylePreprocessorOptions": { + "includePaths": [ + "projects/e2e/workbench-application-platform/common/src/lib" + ] + }, + "scripts": [] + }, + "configurations": { + "now": { + "fileReplacements": [ + { + "replace": "projects/e2e/workbench-application-platform/contact-app/src/environments/environment.ts", + "with": "projects/e2e/workbench-application-platform/contact-app/src/environments/environment.now.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "e2e/workbench-application-platform/contact-app:build" + }, + "configurations": { + "now": { + "browserTarget": "e2e/workbench-application-platform/contact-app:build:now" + } + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/e2e/workbench-application-platform/contact-app/tsconfig.app.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "e2e/workbench-application-platform/communication-app": { + "root": "projects/e2e/workbench-application-platform/communication-app", + "sourceRoot": "projects/e2e/workbench-application-platform/communication-app/src", + "projectType": "application", + "prefix": "app", + "schematics": { + "@schematics/angular:component": { + "styleext": "scss" + } + }, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/e2e/workbench-application-platform/communication-app", + "index": "projects/e2e/workbench-application-platform/communication-app/src/index.html", + "main": "projects/e2e/workbench-application-platform/communication-app/src/main.ts", + "polyfills": "projects/e2e/workbench-application-platform/communication-app/src/polyfills.ts", + "tsConfig": "projects/e2e/workbench-application-platform/communication-app/tsconfig.app.json", + "assets": [ + "projects/e2e/workbench-application-platform/communication-app/src/assets", + "projects/e2e/workbench-application-platform/communication-app/src/now.json" + ], + "styles": [ + "projects/e2e/workbench-application-platform/communication-app/src/styles.scss" + ], + "stylePreprocessorOptions": { + "includePaths": [ + "projects/e2e/workbench-application-platform/common/src/lib" + ] + }, + "scripts": [] + }, + "configurations": { + "now": { + "fileReplacements": [ + { + "replace": "projects/e2e/workbench-application-platform/communication-app/src/environments/environment.ts", + "with": "projects/e2e/workbench-application-platform/communication-app/src/environments/environment.now.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "e2e/workbench-application-platform/communication-app:build" + }, + "configurations": { + "now": { + "browserTarget": "e2e/workbench-application-platform/communication-app:build:now" + } + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/e2e/workbench-application-platform/communication-app/tsconfig.app.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "e2e/workbench-application-platform/dev-tools-app": { + "root": "projects/e2e/workbench-application-platform/dev-tools-app", + "sourceRoot": "projects/e2e/workbench-application-platform/dev-tools-app/src", + "projectType": "application", + "prefix": "app", + "schematics": { + "@schematics/angular:component": { + "styleext": "scss" + } + }, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/e2e/workbench-application-platform/dev-tools-app", + "index": "projects/e2e/workbench-application-platform/dev-tools-app/src/index.html", + "main": "projects/e2e/workbench-application-platform/dev-tools-app/src/main.ts", + "polyfills": "projects/e2e/workbench-application-platform/dev-tools-app/src/polyfills.ts", + "tsConfig": "projects/e2e/workbench-application-platform/dev-tools-app/tsconfig.app.json", + "assets": [ + "projects/e2e/workbench-application-platform/dev-tools-app/src/assets", + "projects/e2e/workbench-application-platform/dev-tools-app/src/now.json" + ], + "styles": [ + "projects/e2e/workbench-application-platform/dev-tools-app/src/styles.scss" + ], + "stylePreprocessorOptions": { + "includePaths": [ + "projects/e2e/workbench-application-platform/common/src/lib" + ] + }, + "scripts": [] + }, + "configurations": { + "now": { + "fileReplacements": [ + { + "replace": "projects/e2e/workbench-application-platform/dev-tools-app/src/environments/environment.ts", + "with": "projects/e2e/workbench-application-platform/dev-tools-app/src/environments/environment.now.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "e2e/workbench-application-platform/dev-tools-app:build" + }, + "configurations": { + "now": { + "browserTarget": "e2e/workbench-application-platform/dev-tools-app:build:now" + } + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/e2e/workbench-application-platform/dev-tools-app/tsconfig.app.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "e2e/workbench-application-platform/testing-app": { + "root": "projects/e2e/workbench-application-platform/testing-app", + "sourceRoot": "projects/e2e/workbench-application-platform/testing-app/src", + "projectType": "application", + "prefix": "app", + "schematics": { + "@schematics/angular:component": { + "styleext": "scss" + } + }, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/e2e/workbench-application-platform/testing-app", + "index": "projects/e2e/workbench-application-platform/testing-app/src/index.html", + "main": "projects/e2e/workbench-application-platform/testing-app/src/main.ts", + "polyfills": "projects/e2e/workbench-application-platform/testing-app/src/polyfills.ts", + "tsConfig": "projects/e2e/workbench-application-platform/testing-app/tsconfig.app.json", + "assets": [ + "projects/e2e/workbench-application-platform/testing-app/src/assets" + ], + "styles": [ + "projects/e2e/workbench-application-platform/testing-app/src/styles.scss" + ], + "stylePreprocessorOptions": { + "includePaths": [ + "projects/e2e/workbench-application-platform/common/src/lib" + ] + }, + "scripts": [] + }, + "configurations": { + "now": { + "fileReplacements": [ + { + "replace": "projects/e2e/workbench-application-platform/testing-app/src/environments/environment.ts", + "with": "projects/e2e/workbench-application-platform/testing-app/src/environments/environment.now.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "e2e/workbench-application-platform/testing-app:build" + }, + "configurations": { + "now": { + "browserTarget": "e2e/workbench-application-platform/testing-app:build:now" + } + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/e2e/workbench-application-platform/testing-app/tsconfig.app.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "e2e/workbench-application-platform/common": { + "root": "projects/e2e/workbench-application-platform/common", + "sourceRoot": "projects/e2e/workbench-application-platform/common/src", + "projectType": "library", + "prefix": "sci", + "architect": { + "build": { + "builder": "@angular-devkit/build-ng-packagr:build", + "options": { + "tsConfig": "projects/e2e/workbench-application-platform/common/tsconfig.lib.json", + "project": "projects/e2e/workbench-application-platform/common/ng-package.json" + }, + "configurations": { + "now": {} + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/e2e/workbench-application-platform/common/tsconfig.lib.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } } }, "defaultProject": "@scion/workbench" diff --git a/package-lock.json b/package-lock.json index 51c692e74..1895ff455 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "scion", - "version": "1.0.0", + "version": "0.0.0-beta.12", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -586,12 +586,12 @@ } }, "@babel/generator": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.1.6.tgz", - "integrity": "sha512-brwPBtVvdYdGxtenbQgfCdDPmtkmUBZPjUoK5SXJEBuHaA5BCubh9ly65fzXz7R6o5rA76Rs22ES8Z+HCc0YIQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", + "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", "dev": true, "requires": { - "@babel/types": "^7.1.6", + "@babel/types": "^7.2.0", "jsesc": "^2.5.1", "lodash": "^4.17.10", "source-map": "^0.5.0", @@ -661,9 +661,9 @@ } }, "@babel/parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.6.tgz", - "integrity": "sha512-dWP6LJm9nKT6ALaa+bnL247GHHMWir3vSlZ2+IHgHgktZQx0L3Uvq2uAWcuzIe+fujRsYWBW2q622C5UvGK9iQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-M74+GvK4hn1eejD9lZ7967qAwvqTZayQa3g10ag4s9uewgR7TKjeaT0YMyoq+gVfKYABiWZ4MQD701/t5e1Jhg==", "dev": true }, "@babel/template": { @@ -718,9 +718,9 @@ } }, "@babel/types": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.6.tgz", - "integrity": "sha512-DMiUzlY9DSjVsOylJssxLHSgj6tWM9PRFJOGW/RaOglVOK9nzTxoOMfTfRQXGUCUQ/HmlG2efwC+XqUEJ5ay4w==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", + "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1133,9 +1133,9 @@ } }, "ansi-colors": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.1.tgz", - "integrity": "sha512-Xt+zb6nqgvV9SWAVp0EG3lRsHcbq5DDgqjPPz6pwgtj6RKz65zGXMNa82oJfOSBA/to6GmRP7Dr+6o+kbApTzQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, "ansi-escapes": { @@ -1233,6 +1233,12 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-filter": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", + "dev": true + }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -1240,9 +1246,21 @@ "dev": true }, "array-flatten": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz", - "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-map": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", + "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", + "dev": true + }, + "array-reduce": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", + "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", "dev": true }, "array-slice": { @@ -2061,9 +2079,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000910", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000910.tgz", - "integrity": "sha512-u/nxtHGAzCGZzIxt3dA/tpSPOcirBZFWKwz1EPz4aaupnBI2XR0Rbr74g0zc6Hzy41OEM4uMoZ38k56TpYAWjQ==", + "version": "1.0.30000916", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000916.tgz", + "integrity": "sha512-D6J9jloPm2MPkg0PXcODLMQAJKkeixKO9xhqTUMvtd44MtTYMyyDXPQ2Lk9IgBq5FH0frwiPa/N/w8ncQf7kIQ==", "dev": true }, "canonical-path": { @@ -2829,6 +2847,15 @@ } } }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", @@ -3082,9 +3109,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.84.tgz", - "integrity": "sha512-IYhbzJYOopiTaNWMBp7RjbecUBsbnbDneOP86f3qvS0G0xfzwNSvMJpTrvi5/Y1gU7tg2NAgeg8a8rCYvW9Whw==", + "version": "1.3.88", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.88.tgz", + "integrity": "sha512-UPV4NuQMKeUh1S0OWRvwg0PI8ASHN9kBC8yDTk1ROXLC85W5GnhTRu/MZu3Teqx3JjlQYuckuHYXSUSgtb3J+A==", "dev": true }, "elliptic": { @@ -3226,6 +3253,30 @@ "is-arrayish": "^0.2.1" } }, + "es-abstract": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es6-promise": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", @@ -3438,7 +3489,7 @@ }, "expand-range": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", + "resolved": "http://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", "dev": true, "requires": { @@ -3497,7 +3548,7 @@ }, "expand-range": { "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "resolved": "http://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { @@ -3586,7 +3637,7 @@ "dependencies": { "array-flatten": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true } @@ -4543,6 +4594,12 @@ "rimraf": "2" } }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -4789,9 +4846,9 @@ }, "dependencies": { "ajv": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz", - "integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", + "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", @@ -4802,6 +4859,15 @@ } } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -4840,6 +4906,12 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -4889,9 +4961,9 @@ } }, "hash.js": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", - "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -4909,6 +4981,12 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "hoek": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.4.tgz", + "integrity": "sha512-Alr4ZQgoMlnere5FZJsIyfIjORBqZll5POhDsF4q64dPuJR6rNxXdDxtHSQq8OXRurhmx+PWYEE8bXRROY8h0w==", + "dev": true + }, "hosted-git-info": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", @@ -5360,6 +5438,12 @@ "builtin-modules": "^1.0.0" } }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, "is-ci": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", @@ -5389,6 +5473,12 @@ } } }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -5573,6 +5663,15 @@ "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", "dev": true }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, "is-retry-allowed": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", @@ -5585,6 +5684,15 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -5624,6 +5732,15 @@ "buffer-alloc": "^1.2.0" } }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "dev": true, + "requires": { + "punycode": "2.x.x" + } + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5933,12 +6050,28 @@ "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", "dev": true }, + "joi": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz", + "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==", + "dev": true, + "requires": { + "hoek": "5.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, "js-base64": { "version": "2.4.9", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.9.tgz", "integrity": "sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ==", "dev": true }, + "js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -6020,6 +6153,12 @@ "graceful-fs": "^4.1.6" } }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -6079,7 +6218,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true } @@ -6121,9 +6260,9 @@ }, "dependencies": { "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", "dev": true }, "source-map": { @@ -6437,13 +6576,13 @@ "dev": true }, "lru-cache": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.4.tgz", - "integrity": "sha512-EPstzZ23znHUVLKj+lcXO1KvZkrlw+ZirdwvOmnAnA/1PB4ggyXJ77LRkCqkff+ShQ+cqoxCxLQOh4cKITO5iA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { "pseudomap": "^1.0.2", - "yallist": "^3.0.2" + "yallist": "^2.1.2" } }, "magic-string": { @@ -6550,6 +6689,12 @@ "readable-stream": "^2.0.1" } }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true + }, "meow": { "version": "3.7.0", "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", @@ -6866,28 +7011,28 @@ }, "dependencies": { "autoprefixer": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.3.1.tgz", - "integrity": "sha512-DY9gOh8z3tnCbJ13JIWaeQsoYncTGdsrgCceBaQSIL4nvdrLxgbRSBPevg2XbX7u4QCSfLheSJEEIUUSlkbx6Q==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.4.2.tgz", + "integrity": "sha512-tYQYJvZvqlJCzF+BLC//uAcdT/Yy4ik9bwZRXr/EehUJ/bjjpTthsWTy8dpowdoIE1sLCDf1ch4Eb2cOSzZC9w==", "dev": true, "requires": { - "browserslist": "^4.3.3", - "caniuse-lite": "^1.0.30000898", + "browserslist": "^4.3.5", + "caniuse-lite": "^1.0.30000914", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^7.0.5", + "postcss": "^7.0.6", "postcss-value-parser": "^3.3.1" } }, "browserslist": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.4.tgz", - "integrity": "sha512-u5iz+ijIMUlmV8blX82VGFrB9ecnUg5qEt55CMZ/YJEhha+d8qpBfOFuutJ6F/VKRXjZoD33b6uvarpPxcl3RA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.5.tgz", + "integrity": "sha512-z9ZhGc3d9e/sJ9dIx5NFXkKoaiQTnrvrMsN3R1fGb1tkWWNSz12UewJn9TNxGo1l7J23h0MRaPmk7jfeTZYs1w==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000899", - "electron-to-chromium": "^1.3.82", - "node-releases": "^1.0.1" + "caniuse-lite": "^1.0.30000912", + "electron-to-chromium": "^1.3.86", + "node-releases": "^1.0.5" } }, "find-up": { @@ -7081,9 +7226,9 @@ } }, "node-releases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.0.4.tgz", - "integrity": "sha512-GqRV9GcHw8JCRDaP/JoeNMNzEGzHAknMvIHqMb2VeTOmg1Cf9+ej8bkV12tHfzWHQMCkQ5zUFgwFUkfraynNCw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.0.tgz", + "integrity": "sha512-+qV91QMDBvARuPxUEfI/mRF/BY+UAkTIn3pvmvM2iOLIRvv6RNYklFXBgrkky6P1wXUqQW1P3qKlWxxy4JZbfg==", "dev": true, "requires": { "semver": "^5.3.0" @@ -7217,7 +7362,7 @@ }, "tough-cookie": { "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "resolved": "http://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "dev": true, "requires": { @@ -7271,6 +7416,12 @@ "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", "dev": true }, + "now": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/now/-/now-13.0.4.tgz", + "integrity": "sha512-o7uS6JEdlslP021/zC/5VzrXuCLfFak43AbjSq3l4QcRlyUyXq40hZiLcSsk6m+oZeFY7RpDHWg0vEMYrYLQZQ==", + "dev": true + }, "npm-package-arg": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz", @@ -7303,6 +7454,77 @@ "ssri": "^5.2.4" } }, + "npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -7391,6 +7613,12 @@ } } }, + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "dev": true + }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -7617,9 +7845,9 @@ } }, "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.7.tgz", + "integrity": "sha512-3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ==", "dev": true }, "parallel-transform": { @@ -7722,7 +7950,7 @@ }, "path-browserify": { "version": "0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "resolved": "http://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", "dev": true }, @@ -7802,6 +8030,12 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "pidtree": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz", + "integrity": "sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==", + "dev": true + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -7928,9 +8162,9 @@ }, "dependencies": { "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", "dev": true }, "postcss": { @@ -7989,9 +8223,9 @@ "dev": true }, "progress": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz", - "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "promise": { @@ -8243,9 +8477,9 @@ } }, "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", "dev": true }, "ms": { @@ -8545,7 +8779,7 @@ }, "regexpu-core": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", "dev": true, "requires": { @@ -8581,7 +8815,7 @@ }, "regjsparser": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "resolved": "http://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, "requires": { @@ -8677,7 +8911,7 @@ }, "resolve": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true }, @@ -8939,6 +9173,12 @@ "aproba": "^1.1.1" } }, + "rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", + "dev": true + }, "rxjs": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", @@ -9271,6 +9511,18 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shell-quote": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", + "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", + "dev": true, + "requires": { + "array-filter": "~0.0.0", + "array-map": "~0.0.0", + "array-reduce": "~0.0.0", + "jsonify": "~0.0.0" + } + }, "shelljs": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", @@ -9617,9 +9869,9 @@ "dev": true }, "spdx-correct": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.2.tgz", - "integrity": "sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -9851,9 +10103,20 @@ "strip-ansi": "^3.0.0" } }, + "string.prototype.padend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz", + "integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.4.3", + "function-bind": "^1.0.2" + } + }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -9987,9 +10250,9 @@ "dev": true }, "tapable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.0.tgz", - "integrity": "sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", + "integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==", "dev": true }, "tar": { @@ -10041,9 +10304,9 @@ } }, "terser": { - "version": "3.10.12", - "resolved": "https://registry.npmjs.org/terser/-/terser-3.10.12.tgz", - "integrity": "sha512-3ODPC1eVt25EVNb04s/PkHxOmzKBQUF6bwwuR6h2DbEF8/j265Y1UkwNtOk9am/pRxfJ5HPapOlUlO6c16mKQQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.11.0.tgz", + "integrity": "sha512-5iLMdhEPIq3zFWskpmbzmKwMQixKmTYwY3Ox9pjtSklBLnHiuQ0GKJLhL1HSYtyffHM3/lDIFBnb82m9D7ewwQ==", "dev": true, "requires": { "commander": "~2.17.1", @@ -10311,6 +10574,23 @@ "repeat-string": "^1.6.1" } }, + "topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "dev": true, + "requires": { + "hoek": "6.x.x" + }, + "dependencies": { + "hoek": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.2.tgz", + "integrity": "sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q==", + "dev": true + } + } + }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -10446,7 +10726,7 @@ }, "tty-browserify": { "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, @@ -10861,7 +11141,7 @@ }, "vm-browserify": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "resolved": "http://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", "dev": true, "requires": { @@ -10874,6 +11154,27 @@ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, + "wait-on": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-3.2.0.tgz", + "integrity": "sha512-QUGNKlKLDyY6W/qHdxaRlXUAgLPe+3mLL/tRByHpRNcHs/c7dZXbu+OnJWGNux6tU1WFh/Z8aEwvbuzSAu79Zg==", + "dev": true, + "requires": { + "core-js": "^2.5.7", + "joi": "^13.0.0", + "minimist": "^1.2.0", + "request": "^2.88.0", + "rx": "^4.1.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", @@ -10894,6 +11195,11 @@ "minimalistic-assert": "^1.0.0" } }, + "web-animations-js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/web-animations-js/-/web-animations-js-2.3.1.tgz", + "integrity": "sha1-Om2bwVGWN3qQ+OKAP6UmIWWwRRA=" + }, "webdriver-js-extender": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", @@ -10990,9 +11296,9 @@ }, "dependencies": { "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", "dev": true } } @@ -11126,9 +11432,9 @@ } }, "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", "dev": true }, "ms": { @@ -11496,9 +11802,9 @@ "dev": true }, "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true }, "yargs": { diff --git a/package.json b/package.json index f3f0980da..0218c8dba 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "scion", "version": "0.0.0-beta.12", - "description": "SCION Workbench helps to build multi-view web applications and integrates separate micro frontends into a consistent rich web application. Views are shown within tabs which can be flexible arranged and dragged around by the user.", + "description": "SCION Workbench helps to build multi-view web applications and integrates separate micro frontends into a consistent rich web application.", "license": "EPL-2.0", "homepage": "https://github.com/SchweizerischeBundesbahnen/scion-workbench", "bugs": { @@ -12,35 +12,85 @@ "url": "https://github.com/SchweizerischeBundesbahnen/scion-workbench" }, "scripts": { - "ng": "ng", - "build": "npm run build-mouse-dispatcher && npm run build-dimension && npm run build-viewport && npm run build-workbench", - "build-workbench": "ng build --prod @scion/workbench && cp -r projects/scion/workbench/src/theme/** dist/scion/workbench", - "build-mouse-dispatcher": "ng build --prod @scion/mouse-dispatcher", - "build-dimension": "ng build --prod @scion/dimension", - "build-viewport": "ng build --prod @scion/viewport", - "test": "npm run test-workbench && npm run test-viewport", - "test-workbench": "ng test @scion/workbench --watch=false", - "test-viewport": "ng test @scion/viewport --watch=false", - "lint": "npm run lint-workbench && npm run lint-mouse-dispatcher && npm run lint-dimension && npm run lint-viewport", - "lint-workbench": "ng lint @scion/workbench", - "lint-mouse-dispatcher": "ng lint @scion/mouse-dispatcher", - "lint-dimension": "ng lint @scion/dimension", - "lint-viewport": "ng lint @scion/viewport", - "e2e": "ng e2e" + "build": "run-s build:*", + "build:mouse-dispatcher": "ng build --prod @scion/mouse-dispatcher", + "build:dimension": "ng build --prod @scion/dimension", + "build:viewport": "ng build --prod @scion/viewport", + "build:workbench": "ng build --prod @scion/workbench && cp -r projects/scion/workbench/src/theme/** dist/scion/workbench", + "build:workbench-application-platform.api": "ng build --prod @scion/workbench-application-platform.api", + "build:workbench-application-platform": "ng build --prod @scion/workbench-application-platform", + "build:workbench-application.core": "ng build --prod @scion/workbench-application.core", + "build:workbench-application.angular": "ng build --prod @scion/workbench-application.angular", + + "test": "run-s test:*", + "test:workbench": "ng test @scion/workbench --watch=false", + "test:workbench-application-platform": "ng test @scion/workbench-application-platform --watch=false", + "test:workbench-application.core": "ng test @scion/workbench-application.core --watch=false", + "test:workbench-application.angular": "ng test @scion/workbench-application.angular --watch=false", + "test:viewport": "ng test @scion/viewport --watch=false", + + "lint": "run-p lint:*", + "lint:workbench": "ng lint @scion/workbench", + "lint:workbench-application-platform.api": "ng lint @scion/workbench-application-platform.api", + "lint:workbench-application-platform": "ng lint @scion/workbench-application-platform", + "lint:workbench-application.core": "ng lint @scion/workbench-application.core", + "lint:workbench-application.angular": "ng lint @scion/workbench-application.angular", + "lint:mouse-dispatcher": "ng lint @scion/mouse-dispatcher", + "lint:dimension": "ng lint @scion/dimension", + "lint:viewport": "ng lint @scion/viewport", + + "e2e:workbench-application-platform:e2e": "run-p -r e2e:workbench-application-platform:serve e2e:workbench-application-platform:await-then-exec", + "e2e:workbench-application-platform:await-then-exec": "run-s e2e:workbench-application-platform:await e2e:workbench-application-platform:exec:test-runner", + "e2e:workbench-application-platform:build-now": "run-s e2e:workbench-application-platform:build-now:*", + "e2e:workbench-application-platform:lint": "run-p e2e:workbench-application-platform:lint:*", + "e2e:workbench-application-platform:serve": "run-p -r e2e:workbench-application-platform:serve:*", + "e2e:workbench-application-platform:await": "run-s e2e:workbench-application-platform:await:*", + + "e2e:workbench-application-platform:lint:test-runner": "ng lint e2e/workbench-application-platform/test-runner", + "e2e:workbench-application-platform:exec:test-runner": "ng e2e e2e/workbench-application-platform/test-runner --baseUrl http://localhost:5000", + + "e2e:workbench-application-platform:build-now:common": "ng build --configuration=now e2e/workbench-application-platform/common", + "e2e:workbench-application-platform:lint:common": "ng lint e2e/workbench-application-platform/common", + + "e2e:workbench-application-platform:build-now:host-app": "ng build --configuration=now e2e/workbench-application-platform/host-app", + "e2e:workbench-application-platform:lint:host-app": "ng lint e2e/workbench-application-platform/host-app", + "e2e:workbench-application-platform:serve:host-app": "ng serve e2e/workbench-application-platform/host-app --aot --port 5000", + "e2e:workbench-application-platform:await:host-app": "wait-on http-get://localhost:5000", + + "e2e:workbench-application-platform:build-now:contact-app": "ng build --configuration=now e2e/workbench-application-platform/contact-app", + "e2e:workbench-application-platform:lint:contact-app": "ng lint e2e/workbench-application-platform/contact-app", + "e2e:workbench-application-platform:serve:contact-app": "ng serve e2e/workbench-application-platform/contact-app --aot --port 5001", + "e2e:workbench-application-platform:await:contact-app": "wait-on http-get://localhost:5001", + + "e2e:workbench-application-platform:build-now:communication-app": "ng build --configuration=now e2e/workbench-application-platform/communication-app", + "e2e:workbench-application-platform:lint:communication-app": "ng lint e2e/workbench-application-platform/communication-app", + "e2e:workbench-application-platform:serve:communication-app": "ng serve e2e/workbench-application-platform/communication-app --aot --port 5002", + "e2e:workbench-application-platform:await:communication-app": "wait-on http-get://localhost:5002", + + "e2e:workbench-application-platform:build-now:dev-tools-app": "ng build --configuration=now e2e/workbench-application-platform/dev-tools-app", + "e2e:workbench-application-platform:lint:dev-tools-app": "ng lint e2e/workbench-application-platform/dev-tools-app", + "e2e:workbench-application-platform:serve:dev-tools-app": "ng serve e2e/workbench-application-platform/dev-tools-app --aot --port 5003", + "e2e:workbench-application-platform:await:dev-tools-app": "wait-on http-get://localhost:5003", + + "e2e:workbench-application-platform:lint:testing-app": "ng lint e2e/workbench-application-platform/testing-app", + "e2e:workbench-application-platform:serve:testing-app": "ng serve e2e/workbench-application-platform/testing-app --aot --port 5004", + "e2e:workbench-application-platform:await:testing-app": "wait-on http-get://localhost:5004" }, "private": true, "dependencies": { - "@angular/core": "7.0.0", - "@angular/common": "7.0.0", + "@angular/animations": "7.0.0", "@angular/cdk": "7.0.1", + "@angular/common": "7.0.0", "@angular/compiler": "7.0.0", - "@angular/animations": "7.0.0", + "@angular/core": "7.0.0", "@angular/forms": "7.0.0", "@angular/platform-browser": "7.0.0", "@angular/platform-browser-dynamic": "7.0.0", "@angular/router": "7.0.0", "core-js": "2.5.7", + "js-sha256": "0.9.0", "rxjs": "6.3.3", + "web-animations-js": "2.3.1", "zone.js": "0.8.26" }, "devDependencies": { @@ -61,6 +111,8 @@ "karma-jasmine": "1.1.2", "karma-jasmine-html-reporter": "1.3.1", "ng-packagr": "4.4.0", + "now": "13.0.4", + "npm-run-all": "4.1.5", "protractor": "5.4.1", "puppeteer": "1.10.0", "rxjs-tslint": "0.1.5", @@ -68,7 +120,8 @@ "tsickle": "0.33.0", "tslib": "1.9.3", "tslint": "5.11.0", - "typescript": "3.1.3" + "typescript": "3.1.3", + "wait-on": "3.2.0" }, "repository": { "type": "git", diff --git a/projects/e2e/workbench-application-platform/common/ng-package.json b/projects/e2e/workbench-application-platform/common/ng-package.json new file mode 100644 index 000000000..264a67984 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/ng-package.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../../../dist/scion/e2e/common", + "lib": { + "entryFile": "src/public_api.ts" + }, + "whitelistedNonPeerDependencies": ["@scion"] +} diff --git a/projects/e2e/workbench-application-platform/common/package.json b/projects/e2e/workbench-application-platform/common/package.json new file mode 100644 index 000000000..23765c793 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/package.json @@ -0,0 +1,35 @@ +{ + "name": "@scion/e2e/common", + "version": "0.0.0-beta.12", + "description": "Contains a collection of UI components, styles and services used by E2E application.", + "license": "EPL-2.0", + "private": true, + "publishConfig": { + "access": "private" + }, + "homepage": "https://github.com/SchweizerischeBundesbahnen/scion-workbench", + "bugs": { + "url": "https://github.com/SchweizerischeBundesbahnen/scion-workbench/issues" + }, + "author": { + "name": "SCION Workbench contributors", + "url": "https://github.com/SchweizerischeBundesbahnen/scion-workbench" + }, + "dependencies": { + }, + "peerDependencies": { + "@angular/common": "^6.0.0-rc.0 || ^6.0.0 || ^7.0.0-rc.0 || ^7.0.0", + "@angular/core": "^6.0.0-rc.0 || ^6.0.0 || ^7.0.0-rc.0 || ^7.0.0", + "@angular/cdk": "^6.0.0-rc.0 || ^6.0.0 || ^7.0.0-rc.0 || ^7.0.0", + "@scion/viewport": "0.0.0-beta.12", + "@scion/dimension": "0.0.0-beta.12", + "@scion/workbench-application-platform.api": "0.0.0-beta.12", + "rxjs": "^6.0.0" + }, + "keywords": [ + ], + "repository": { + "type": "git", + "url": "git+https://github.com/SchweizerischeBundesbahnen/scion-workbench.git" + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/_common.scss b/projects/e2e/workbench-application-platform/common/src/lib/_common.scss new file mode 100644 index 000000000..c2ff7db87 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/_common.scss @@ -0,0 +1,197 @@ +@function primaryColor($opacity: 1) { + @return rgba(55, 95, 156, $opacity); +} + +@function accentColor($opacity: 1) { + @return rgba(85, 85, 85, $opacity); +} + +@function errorColor($opacity: 1) { + @return rgba(255, 55, 55, $opacity); +} + +/** + * IE does not support align-items on grid containers. Therefore, we use align-self on child elements instead. + */ +@mixin grid-container-align-items($align) { + > * { + align-self: $align; + } +} + +/** + * Installs the application theme. + */ +@mixin app-theme() { + @import url('https://fonts.googleapis.com/css?family=Roboto:normal,bold,italic,bolditalic|Roboto+Mono'); + @import url('https://fonts.googleapis.com/icon?family=Material+Icons'); + + body { + font-family: Roboto, Arial, sans-serif; + font-size: .9em; + box-sizing: border-box; + + input, textarea, select { + @include input-field; + } + + a { + @include link; + } + + button.material-icons { + @include mat-icon-button; + } + + button:not(.material-icons) { + @include button; + } + } +} + +@function app-padding() { + @return 1em; +} + +@mixin position($position, $top, $right, $bottom, $left) { + position: $position; + top: $top; + right: $right; + bottom: $bottom; + left: $left; +} + +/** + * Provides the styling for a panel with HTML button elements. + */ +@mixin button-bar() { + display: flex; + justify-content: flex-end; + border-top: 1px solid rgba(50, 50, 50, .3); + background-color: rgba(50, 50, 50, .075); + margin-top: 1em; + padding: .5em 1em; + + > button { + @include button(); + + &:not(:first-child) { + margin-left: .2em; + } + + &:not(:last-child) { + margin-right: .2em; + } + } +} + +/** + * Provides the styling for a Material icon button. + */ +@mixin mat-icon-button() { + background-color: transparent; + border: none; + cursor: pointer; + outline: none; + color: inherit; + padding: 0 0 0 .25em; + user-select: none; + + &:focus, &:hover { + color: primaryColor(); + } +} + +/** + * Provides the styling for a button. + */ +@mixin button() { + cursor: pointer; + padding: .5em 1.5em; + font-family: inherit; + color: rgba(51, 51, 51, .8); + border: 1px solid rgba(51, 51, 51, .5); + border-radius: 3px; + background-color: rgb(255, 255, 255); + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + + &:focus, &:active { + border-color: primaryColor(); + color: primaryColor(); + outline: 0; + box-shadow: 0 0 8px 0 primaryColor(.35); + } + + &:disabled { + color: rgba(51, 51, 51, .5); + border-style: dotted; + cursor: auto; + } +} + +/** + * Provides the styling for an input field. + */ +@mixin input-field($radius: 2px) { + border: 1px solid accentColor(.25); + border-radius: $radius; + padding: .5em; + outline: 0; + font-family: inherit; + font-size: inherit; + + &:focus-within { + border-color: primaryColor(); + box-shadow: 0 0 6px 0 primaryColor(.35); + } + + &.inline-editable:not(.ng-invalid) { + &:not(:focus-within):not(:hover):not(:active) { + border: 1px solid transparent; + } + + &:hover:not(:active):not(:focus-within) { + cursor: pointer; + } + } + + &.ng-invalid.ng-touched { + border-color: errorColor(1); + box-shadow: 0 0 6px 0 errorColor(.35); + } + + &[readonly] { + color: accentColor(.5); + } + + &[type="checkbox"] { + margin: .75em 0; + } +} + +@mixin chip($border-color, $background-color, $color, $borderStyle: solid) { + border: 1px $borderStyle $border-color; + background-color: $background-color; + color: $color; + border-radius: 3px; + padding: .25em .5em; + font-size: smaller; + user-select: none; + margin-bottom: .25em; + &:not(:last-child) { + margin-right: .25em; + } +} + +/** + * Provides the styling for a link. + */ +@mixin link($radius: 2px) { + color: rgb(55, 95, 156); + text-decoration: none; + outline: none; + + &:hover, &:focus { + text-decoration: underline; + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion-item.directive.ts b/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion-item.directive.ts new file mode 100644 index 000000000..4dd80a954 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion-item.directive.ts @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Directive, Input, OnInit, TemplateRef } from '@angular/core'; + +/** + * Use this directive to model an accordion item for {SciAccordionComponent}. + * The host element of this modelling directive must be a . + * + * --- + * Example usage: + * + * + * + * + * + * ... + * + * + * + * + * ... + * + * + * + */ +@Directive({selector: 'ng-template[sciAccordionItem]'}) +export class SciAccordionItemDirective implements OnInit { + + /** + * Optional key to identify this item and is used as key for the {TrackBy} function. + */ + @Input() + public key: string; + + /** + * Provide template(s) to be rendered as actions of this list item. + */ + @Input() + public panel: TemplateRef; + + /** + * Indicates whether to expand this item. + */ + @Input() + public expanded: boolean; + + /** + * Specifies CSS class(es) added to the
and elements, e.g. used for e2e testing. + */ + @Input() + public cssClass: string | string[]; + + constructor(public readonly template: TemplateRef) { + } + + public ngOnInit(): void { + if (!this.panel) { + throw Error('[NullPanelError] No panel template specified for `sciAccordionItem`'); + } + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion.component.html b/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion.component.html new file mode 100644 index 000000000..6a5222c05 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion.component.html @@ -0,0 +1,21 @@ + +
+
+
+
+ +
+ {{cdkAccordionItem.expanded ? 'expand_less' : 'expand_more'}} +
+
+ +
+
+
+
diff --git a/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion.component.scss b/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion.component.scss new file mode 100644 index 000000000..61d866a08 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion.component.scss @@ -0,0 +1,79 @@ +@import '../common'; + +$diamond-height: 8; +$border-color: accentColor(.2); +$background-color: rgb(249, 249, 249); + +:host { + display: flex; + + > sci-viewport { + flex: auto; + border: 1px solid accentColor(.25); + border-radius: 5px; + + section.accordion-item { + flex: none; + padding: 1em; + + &:not(:first-child) { + border-top: 1px solid accentColor(.25); + } + + &:last-child { + border-bottom: 1px solid accentColor(.25); + } + + > header { + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + + > div { + flex: auto; + } + + > span.toggle { + flex: none; + margin-left: .5em; + user-select: none; + } + } + + > section { + position: relative; // positioning anchor for the diamond + + background-color: $background-color; + border-radius: 5px; + border: 1px solid $border-color; + padding: 1em; + margin: 1em -.5em -.5em -.5em; + + //::before is used as diamond-border + //::after is used as diamond-content + &::before, &::after { + content: ''; + display: inline-block; + position: absolute; + border: #{$diamond-height}px solid transparent; + } + + &::before { + top: -#{$diamond-height}px; + left: calc(50px - #{$diamond-height}px); + border-top-width: 0; + border-bottom-color: $border-color; + } + + &::after { + top: -#{$diamond-height - 1}px; + left: calc(50px - #{$diamond-height}px); + border-top-width: 0; + border-bottom-color: $background-color; + } + } + } + } +} + diff --git a/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion.component.ts b/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion.component.ts new file mode 100644 index 000000000..0ae7da1e6 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion.component.ts @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, ContentChildren, Input, QueryList, TrackByFunction, ViewChild } from '@angular/core'; +import { animate, AnimationMetadata, style, transition, trigger } from '@angular/animations'; +import { SciAccordionItemDirective } from './accordion-item.directive'; +import { SciViewportComponent } from '@scion/viewport'; +import { CdkAccordionItem } from '@angular/cdk/accordion'; + +/** + * Component that shows items in an accordion. + * + * An accordion item is contributed as content child in the form of a `` decorated with `sciAccordionItem` directive, + * and its panel modelled in the form of a `` and given as input to its `sciAccordionItem` directive. + * + * --- + * Example of a simple list: + * + * + * + * + * + * ... + * + * + * + * + * ... + * + * + * + */ +@Component({ + selector: 'sci-accordion', + templateUrl: './accordion.component.html', + styleUrls: ['./accordion.component.scss'], + animations: [ + trigger('enter', SciAccordionComponent.provideEnterAnimation()), + ] +}) +export class SciAccordionComponent { + + @ViewChild(SciViewportComponent) + private _viewport: SciViewportComponent; + + @ContentChildren(SciAccordionItemDirective) + public items: QueryList; + + /** + * Whether the accordion should allow multiple expanded accordion items simultaneously. + */ + @Input() + public multi: boolean; + + public trackByFn: TrackByFunction = (index: number, item: SciAccordionItemDirective): any => { + return item.key || item; + }; + + public onToggle(item: CdkAccordionItem, section: HTMLElement): void { + item.toggle(); + item.expanded && setTimeout(() => this._viewport.scrollIntoView(section)); + } + + /** + * Returns animation metadata to expand accordion panel. + */ + private static provideEnterAnimation(): AnimationMetadata[] { + return [ + transition(':enter', [ + style({opacity: 0, height: 0, overflow: 'hidden'}), + animate('125ms ease-out', style({opacity: 1, height: '*'})) + ]), + ]; + } +} + diff --git a/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion.module.ts b/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion.module.ts new file mode 100644 index 000000000..350bfed7f --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/accordion/accordion.module.ts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SciViewportModule } from '@scion/viewport'; +import { SciAccordionComponent } from './accordion.component'; +import { SciAccordionItemDirective } from './accordion-item.directive'; +import { CdkAccordionModule } from '@angular/cdk/accordion'; + +/** + * Provides an accordion component. + */ +@NgModule({ + declarations: [ + SciAccordionComponent, + SciAccordionItemDirective, + ], + imports: [ + CommonModule, + SciViewportModule, + CdkAccordionModule, + ], + exports: [ + SciAccordionComponent, + SciAccordionItemDirective, + ], +}) +export class SciAccordionModule { +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/custom-extension/metadata.ts b/projects/e2e/workbench-application-platform/common/src/lib/custom-extension/metadata.ts new file mode 100644 index 000000000..1dae5d159 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/custom-extension/metadata.ts @@ -0,0 +1,23 @@ +import { ActivityAction } from '@scion/workbench-application-platform.api'; + +/** + * Custom types for activity actions. + */ +export enum CustomActivityActionTypes { + /** + * Action button to show a notification to the user. + */ + CustomNotify = 'custom', +} + +/** + * Shows an activity button to show a notification to the user. + */ +export interface CustomNotifyActivityAction extends ActivityAction { + type: CustomActivityActionTypes.CustomNotify; + properties: { + text: string; + title: string; + cssClass: string | string[]; + }; +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/filter-field/filter-field.component.html b/projects/e2e/workbench-application-platform/common/src/lib/filter-field/filter-field.component.html new file mode 100644 index 000000000..285df41c7 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/filter-field/filter-field.component.html @@ -0,0 +1,2 @@ + + diff --git a/projects/e2e/workbench-application-platform/common/src/lib/filter-field/filter-field.component.scss b/projects/e2e/workbench-application-platform/common/src/lib/filter-field/filter-field.component.scss new file mode 100644 index 000000000..96fbc4d90 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/filter-field/filter-field.component.scss @@ -0,0 +1,26 @@ +@import '../common'; + +:host { + display: inline-flex; + flex-direction: row; + @include input-field(5px); + padding: .25em .5em; + + > input#search { + border: none; + box-shadow: none; + outline: none; + padding: 0; + flex: auto; + width: 0; // allows the input to shrink past UA minimal width + } + + > label.filter-icon { + flex: none; + user-select: none; + } + + &:focus-within > label.filter-icon { + color: primaryColor(); + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/filter-field/filter-field.component.ts b/projects/e2e/workbench-application-platform/common/src/lib/filter-field/filter-field.component.ts new file mode 100644 index 000000000..93b35db66 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/filter-field/filter-field.component.ts @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, ElementRef, EventEmitter, HostBinding, HostListener, Input, OnDestroy, Output, ViewChild } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +/** + * Provides a simple filter control. + */ +@Component({ + selector: 'sci-filter-field', + templateUrl: './filter-field.component.html', + styleUrls: ['./filter-field.component.scss'] +}) +export class SciFilterFieldComponent implements OnDestroy { + + private _destroy$ = new Subject(); + + /** + * Sets focus order in sequential keyboard navigation. + * If not specified, the focus order is according to the position in the document (tabindex=0). + */ + @Input() + public tabindex = 0; + + /** + * Emits on filter change. + */ + @Output() + public filter = new EventEmitter(); + + @ViewChild('input') + private _inputElement: ElementRef; + + @HostBinding('attr.tabindex') + public componentTabindex = -1; // component is not focusable in sequential keyboard navigation, but tabindex (if any) is installed on input field + + public formControl: FormControl; + + constructor() { + this.formControl = new FormControl('', {updateOn: 'change'}); + this.formControl.valueChanges + .pipe(takeUntil(this._destroy$)) + .subscribe(this.filter); + } + + @HostListener('focus') + public focus(): void { + this._inputElement.nativeElement.focus(); + } + + /** + * Invoke to propagate keyboard events to the filter field. + * + * If the keyboard event represents an alphanumeric character, filter text is cleared and the cursor set into the filter field. + * This allows to start filtering without having to focus the filter field, e.g. if another element has the focus. + */ + public onKeydown(event: KeyboardEvent): void { + if (event.ctrlKey || event.altKey || event.shiftKey) { + return; + } + if (!isAlphanumeric(event)) { + return; + } + this.formControl.setValue(''); + this._inputElement.nativeElement.focus(); + event.stopPropagation(); + } + + public ngOnDestroy(): void { + this._destroy$.next(); + } +} + +function isAlphanumeric(event: KeyboardEvent): boolean { + return (/^[a-z0-9]$/i.test(event.key)); +} + +/** + * Creates a regular expression of the given filter text. + */ +export function toFilterRegExp(filterText: string): RegExp | null { + if (!filterText) { + return null; + } + + // Escape the user filter input and add wildcard support + const escapedString = filterText.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + return new RegExp(escapedString, 'i'); +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/filter-field/filter-field.module.ts b/projects/e2e/workbench-application-platform/common/src/lib/filter-field/filter-field.module.ts new file mode 100644 index 000000000..edc45e9d6 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/filter-field/filter-field.module.ts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SciFilterFieldComponent } from './filter-field.component'; +import { ReactiveFormsModule } from '@angular/forms'; + +/** + * Provides a simple filter field. + */ +@NgModule({ + declarations: [ + SciFilterFieldComponent, + ], + imports: [ + CommonModule, + ReactiveFormsModule, + ], + exports: [ + SciFilterFieldComponent, + ], +}) +export class SciFilterFieldModule { +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/list/list-item.directive.ts b/projects/e2e/workbench-application-platform/common/src/lib/list/list-item.directive.ts new file mode 100644 index 000000000..9d793d325 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/list/list-item.directive.ts @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Directive, Input, TemplateRef } from '@angular/core'; + +/** + * Use this directive to model a list item for {SciListComponent}. + * The host element of this modelling directive must be a . + * + * --- + * Example usage: + * + * + * + * + * + * + */ +@Directive({selector: 'ng-template[sciListItem]'}) +export class SciListItemDirective { + + private _actionTemplates: TemplateRef[] = []; + + /** + * Optional key to identify this item and is used to emit selection and internally as key for the {TrackBy} function. + */ + @Input() + public key: string; + + /** + * Provide template(s) to be rendered as actions of this list item. + */ + @Input() + public set actions(actions: TemplateRef | TemplateRef[]) { + this._actionTemplates = (Array.isArray(actions) ? actions : actions && [actions] || []); + } + + constructor(public readonly template: TemplateRef) { + } + + public get actionTemplates(): TemplateRef[] { + return this._actionTemplates; + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/list/list-item/list-item.component.html b/projects/e2e/workbench-application-platform/common/src/lib/list/list-item/list-item.component.html new file mode 100644 index 000000000..48cb5cc3d --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/list/list-item/list-item.component.html @@ -0,0 +1,16 @@ + + + {{active ? 'radio_button_checked' : 'radio_button_unchecked'}} + + + +
+ +
+ + +
    +
  • + +
  • +
diff --git a/projects/e2e/workbench-application-platform/common/src/lib/list/list-item/list-item.component.scss b/projects/e2e/workbench-application-platform/common/src/lib/list/list-item/list-item.component.scss new file mode 100644 index 000000000..ee45d3152 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/list/list-item/list-item.component.scss @@ -0,0 +1,45 @@ +@import '../../common'; + +:host { + display: flex; + align-items: center; + outline: none; + padding: 1em; + + > span.option { + flex: none; + margin-right: .5em; + user-select: none; + } + + > div.item { + flex: auto; + text-overflow: ellipsis; + overflow: hidden; + } + + > ul.actions { + flex: none; + display: flex; + list-style: none; + margin: 0; + padding: 0; + visibility: hidden; + } + + &:hover, &:focus-within { + > ul.actions { + visibility: visible; + } + } + + &.active { + > div.main, > span.option { + color: primaryColor(); + } + } + + &.option { + cursor: pointer; + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/list/list-item/list-item.component.ts b/projects/e2e/workbench-application-platform/common/src/lib/list/list-item/list-item.component.ts new file mode 100644 index 000000000..88e60e7c2 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/list/list-item/list-item.component.ts @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, ElementRef, HostBinding, Input } from '@angular/core'; +import { FocusableOption, FocusOrigin } from '@angular/cdk/a11y'; +import { SciListItemDirective } from '../list-item.directive'; +import { SciListStyle } from '../metadata'; + +@Component({ + selector: 'sci-list-item', + templateUrl: './list-item.component.html', + styleUrls: ['./list-item.component.scss'], +}) +export class SciListItemComponent implements FocusableOption { + + @Input() + public listItem: SciListItemDirective; + + @HostBinding('class.active') + @Input() + public active: boolean; + + @Input() + public style: SciListStyle; + + @HostBinding('attr.disabled') + public disabled: boolean; + + @HostBinding('attr.tabindex') + public tabindex = -1; + + constructor(private _host: ElementRef) { + } + + public focus(origin?: FocusOrigin): void { + this._host.nativeElement.focus(); + } + + @HostBinding('class.option') + public get optionStyle(): boolean { + return this.style === 'option-item'; + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/list/list.component.html b/projects/e2e/workbench-application-platform/common/src/lib/list/list.component.html new file mode 100644 index 000000000..f1bccfeba --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/list/list.component.html @@ -0,0 +1,21 @@ + + + + + +
+ + +
+
+ + + + + + + diff --git a/projects/e2e/workbench-application-platform/common/src/lib/list/list.component.scss b/projects/e2e/workbench-application-platform/common/src/lib/list/list.component.scss new file mode 100644 index 000000000..1141c0e94 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/list/list.component.scss @@ -0,0 +1,35 @@ +@import '../common'; + +:host { + display: flex; + flex-direction: column; + outline: none; + + > sci-filter-field { + flex: none; + + &.top { + margin-bottom: .3em; + } + + &.bottom { + margin-top: .3em; + } + } + + > sci-viewport { + flex: auto; + border: 1px solid accentColor(.25); + border-radius: 5px; + padding: .5em; + + sci-list-item { + &:not(:first-child) { + border-top: 1px solid accentColor(.25); + } + &:last-child { + border-bottom: 1px solid accentColor(.25); + } + } + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/list/list.component.ts b/projects/e2e/workbench-application-platform/common/src/lib/list/list.component.ts new file mode 100644 index 000000000..6668c344a --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/list/list.component.ts @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { AfterViewInit, Component, ContentChildren, EventEmitter, HostBinding, HostListener, Input, OnDestroy, Output, QueryList, TrackByFunction, ViewChild, ViewChildren } from '@angular/core'; +import { FocusKeyManager } from '@angular/cdk/a11y'; +import { SciListItemDirective } from './list-item.directive'; +import { SciListItemComponent } from './list-item/list-item.component'; +import { SciFilterFieldComponent } from '../filter-field/filter-field.component'; +import { Subject } from 'rxjs'; +import { filter, map, takeUntil } from 'rxjs/operators'; +import { SciListStyle } from './metadata'; + +/** + * Component that contains a list of items or options which can be filtered and associated with actions. + * + * List items are contributed as content children in the form of a `` decorated with `sciListItem` directive. + * Actions are modelled in the form of a `` and are inputs for respective `sciListItem` directive. + * + * --- + * Example of a simple list: + * + * + * + * ... + * + * + * + * + * --- + * Example of a list with actions: + * + * + * + * + * + * ... + * + * + * + * + * + * + * + * + */ +@Component({ + selector: 'sci-list', + templateUrl: './list.component.html', + styleUrls: ['./list.component.scss'], +}) +export class SciListComponent implements AfterViewInit, OnDestroy { + + private _focusKeyManager: FocusKeyManager; + private _destroy$ = new Subject(); + + /** + * Specifies where to position the filter field. + */ + @Input() + public filterPosition: 'top' | 'bottom' = 'top'; + + /** + * Specifies whether to render list items or option items. + */ + @Input() + public style: SciListStyle = 'list-item'; + + /** + * Sets focus order in sequential keyboard navigation. + * If not specified, the focus order is according to the position in the document (tabindex=0). + */ + @Input() + public tabindex = 0; + + /** + * Emits filter text on filter change. + */ + @Output() + public filter = new EventEmitter(); + + /** + * Emits selected item key on selection change. + */ + @Output() + public selection = new EventEmitter(); + + @ContentChildren(SciListItemDirective) + public listItems: QueryList; + + @ViewChildren(SciListItemComponent) + private _listItemComponents: QueryList; + + @ViewChild(SciFilterFieldComponent) + private _filterField: SciFilterFieldComponent; + + @HostBinding('attr.tabindex') + public componentTabindex = -1; // component itself is not focusable in sequential keyboard navigation, but tabindex (if any) set to filter field + + @HostListener('keydown', ['$event']) + public onKeydown(event: KeyboardEvent): void { + this._focusKeyManager.onKeydown(event); + } + + @HostListener('focus') + public focus(): void { + this._filterField.focus(); + } + + public ngAfterViewInit(): void { + this._focusKeyManager = new FocusKeyManager(this._listItemComponents); + this._focusKeyManager.change + .pipe( + map(index => this.listItems.toArray()[index]), + filter(Boolean), + takeUntil(this._destroy$) + ) + .subscribe((listItem: SciListItemDirective) => { + this.selection.emit(listItem.key); + }); + } + + public onItemClick(item: SciListItemComponent): void { + this._focusKeyManager.setActiveItem(item); + } + + public onFilter(filterText: string): void { + this._focusKeyManager.setActiveItem(-1); + this.filter.emit(filterText); + } + + public onAnyKey(event: KeyboardEvent): void { + this._filterField.onKeydown(event); + } + + public get activeItem(): SciListItemComponent { + return this._focusKeyManager && this._focusKeyManager.activeItem; + } + + public trackByFn: TrackByFunction = (index: number, item: SciListItemDirective): any => { + return item.key || item; + }; + + public ngOnDestroy(): void { + this._destroy$.next(); + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/list/list.module.ts b/projects/e2e/workbench-application-platform/common/src/lib/list/list.module.ts new file mode 100644 index 000000000..fcfcab8cc --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/list/list.module.ts @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { SciListComponent } from './list.component'; +import { A11yModule } from '@angular/cdk/a11y'; +import { SciFilterFieldModule } from '../filter-field/filter-field.module'; +import { SciListItemComponent } from './list-item/list-item.component'; +import { SciListItemDirective } from './list-item.directive'; +import { SciViewportModule } from '@scion/viewport'; +import { CommonModule } from '@angular/common'; + +/** + * Provides a list component to render a list of items which can be filtered. + */ +@NgModule({ + declarations: [ + SciListComponent, + SciListItemComponent, + SciListItemDirective, + ], + exports: [ + SciListComponent, + SciListItemComponent, + SciListItemDirective, + ], + imports: [ + CommonModule, + SciViewportModule, + SciFilterFieldModule, + A11yModule, + ] +}) +export class SciListModule { +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/list/metadata.ts b/projects/e2e/workbench-application-platform/common/src/lib/list/metadata.ts new file mode 100644 index 000000000..dce6f37be --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/list/metadata.ts @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/** + * Styles for ``. + */ +export declare type SciListStyle = 'list-item' | 'option-item'; diff --git a/projects/e2e/workbench-application-platform/common/src/lib/params-enter/params-enter.component.html b/projects/e2e/workbench-application-platform/common/src/lib/params-enter/params-enter.component.html new file mode 100644 index 000000000..2dc5b8050 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/params-enter/params-enter.component.html @@ -0,0 +1,20 @@ +

{{title}}

+ + + + + + + + + + + + + + + + + + + diff --git a/projects/e2e/workbench-application-platform/common/src/lib/params-enter/params-enter.component.scss b/projects/e2e/workbench-application-platform/common/src/lib/params-enter/params-enter.component.scss new file mode 100644 index 000000000..b633f45dd --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/params-enter/params-enter.component.scss @@ -0,0 +1,34 @@ +@import '../common'; + +:host { + display: grid; + outline: none; + + grid-template-columns: 100px auto; // 2 columns + grid-column-gap: 1em; + grid-row-gap: .5em; + grid-template-rows: auto; + @include grid-container-align-items(center); + + border: 1px solid accentColor(.25); + border-radius: 5px; + padding: 1em; + + &.addable, &.removable { + grid-template-columns: 100px auto 25px; // 3 columns + } + + > h2 { + font-size: 1em; + font-weight: bold; + margin-top: 0; + + grid-column: 1/3; + grid-row: 1/2; + } + + > button.add { + grid-column: 3/4; + grid-row: 1/2; + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/params-enter/params-enter.component.ts b/projects/e2e/workbench-application-platform/common/src/lib/params-enter/params-enter.component.ts new file mode 100644 index 000000000..5625beff8 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/params-enter/params-enter.component.ts @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, ElementRef, HostBinding, Input } from '@angular/core'; +import { FormArray, FormBuilder } from '@angular/forms'; + +export const PARAM_NAME = 'paramName'; +export const PARAM_VALUE = 'paramValue'; + +/** + * Allows to enter parameters. + */ +@Component({ + selector: 'sci-params-enter', + templateUrl: './params-enter.component.html', + styleUrls: ['./params-enter.component.scss'], +}) +export class SciParamsEnterComponent { + + public readonly PARAM_NAME = PARAM_NAME; + public readonly PARAM_VALUE = PARAM_VALUE; + + @Input() + public title: string; + + @Input() + public paramsFormArray: FormArray; + + @Input() + @HostBinding('class.removable') + public removable: boolean; + + @Input() + @HostBinding('class.addable') + public addable: boolean; + + @HostBinding('attr.tabindex') + public tabindex = -1; + + constructor(private _formBuilder: FormBuilder, private _host: ElementRef) { + } + + public onRemove(index: number): void { + this.paramsFormArray.removeAt(index); + + // Focus the component to not loose the focus when the remove button is removed from the DOM. + // Otherwise, if used in a popup, the popup would be closed because no element is focused anymore. + this._host.nativeElement.focus({preventScroll: true}); + } + + public onAdd(): void { + this.paramsFormArray.push(this._formBuilder.group({ + [PARAM_NAME]: this._formBuilder.control(''), + [PARAM_VALUE]: this._formBuilder.control(''), + })); + } + + /** + * Creates a dictionary from the form controls in the given `FormArray`. + */ + public static toParams(formArray: FormArray): { [key: string]: any } { + const params: { [key: string]: any } = {}; + formArray.controls.forEach(formGroup => { + const paramName = formGroup.get(PARAM_NAME).value; + params[paramName] = formGroup.get(PARAM_VALUE).value; + }); + return params; + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/params-enter/params-enter.module.ts b/projects/e2e/workbench-application-platform/common/src/lib/params-enter/params-enter.module.ts new file mode 100644 index 000000000..28f50953a --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/params-enter/params-enter.module.ts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SciParamsEnterComponent } from './params-enter.component'; +import { ReactiveFormsModule } from '@angular/forms'; + +/** + * Allows to enter parameters. + */ +@NgModule({ + declarations: [ + SciParamsEnterComponent + ], + exports: [ + SciParamsEnterComponent, + ], + imports: [ + CommonModule, + ReactiveFormsModule, + ] +}) +export class SciParamsEnterModule { +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/popup-shell/popup-shell-button.directive.ts b/projects/e2e/workbench-application-platform/common/src/lib/popup-shell/popup-shell-button.directive.ts new file mode 100644 index 000000000..ef3788026 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/popup-shell/popup-shell-button.directive.ts @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Directive, EventEmitter, Input, Output, TemplateRef } from '@angular/core'; + +/** + * Use this directive to model a button for {SciPopupComponent}. + * The host element of this modelling directive must be a . + * + * --- + * Example usage: + * + * + * Title + * + * + * ... + * + * OK + * + */ +@Directive({selector: 'ng-template[sciPopupShellButton]'}) +export class SciPopupShellButtonDirective { + + /** + * Specifies if this is the default button, meaning it is enabled only if valid + * and clicked upon enter keystroke. + */ + @Input() + public defaultButton = true; + + /** + * Specifies CSS class(es) added to the + + diff --git a/projects/e2e/workbench-application-platform/common/src/lib/popup-shell/popup-shell.component.scss b/projects/e2e/workbench-application-platform/common/src/lib/popup-shell/popup-shell.component.scss new file mode 100644 index 000000000..b8905ffa6 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/popup-shell/popup-shell.component.scss @@ -0,0 +1,36 @@ +@import '../common'; + +:host { + display: flex; + position: relative; // positioned anchor for close button + flex-direction: column; + padding-top: app-padding(); + + > header { + flex: none; + margin: 0 app-padding() 2em app-padding(); + font-size: 1.1em; + font-weight: bold; + } + + > sci-viewport { + flex: auto; + + div.content { + margin: 0 app-padding(); + display: grid; + } + } + + > div.button-bar { + flex: none; + @include button-bar(); + } + + > button.close { + position: absolute; + top: 5px; + right: 5px; + @include mat-icon-button; + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/popup-shell/popup-shell.component.ts b/projects/e2e/workbench-application-platform/common/src/lib/popup-shell/popup-shell.component.ts new file mode 100644 index 000000000..ac60946f3 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/popup-shell/popup-shell.component.ts @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { AfterViewInit, Component, ContentChild, ContentChildren, HostListener, Input, QueryList } from '@angular/core'; +import { WorkbenchPopup } from '@scion/workbench-application.angular'; +import { SciPopupShellTitleDirective } from './popup-shell-title.directive'; +import { SciPopupShellContentDirective } from './popup-shell-content.directive'; +import { SciPopupShellButtonDirective } from './popup-shell-button.directive'; + +/** + * Component that provides the shell for a popup. + * + * The shell consists of the following elements: + * - close button in the top-right corner + * - title at the top + * - button panel at the bottom with an 'OK' button (enabled if valid) + * + * This component can be used only in a workbench popup context. + * + * Title and content are contributed as content children in the form of a `` decorated with `sciPopupShellTitle` + * directive and `sciPopupShellContent`, respectively. + * + * Content is added to a CSS grid container with a single column. + * + * --- + * Example: + * + * + * Title + * + * + * ... + * + * + */ +@Component({ + selector: 'sci-popup-shell', + templateUrl: './popup-shell.component.html', + styleUrls: ['./popup-shell.component.scss'], +}) +export class SciPopupShellComponent implements AfterViewInit { + + @Input() + public valid: boolean; + + @ContentChild(SciPopupShellTitleDirective) + public title: SciPopupShellTitleDirective; + + @ContentChild(SciPopupShellContentDirective) + public content: SciPopupShellContentDirective; + + @ContentChildren(SciPopupShellButtonDirective) + public buttons: QueryList; + + constructor(private _popup: WorkbenchPopup) { + } + + @HostListener('keydown.enter', ['$event']) + public onEnter(event: Event): void { + this.valid && this.buttons.forEach(button => button.defaultButton && button.onClick(event)); + } + + public onClose(): void { + this._popup.close(); + } + + public ngAfterViewInit(): void { + if (!this.title) { + throw Error('[NullTitleError] No title template decorated with `sciPopupShellTitle` directive modelled as content child of \u02C2sci-popup-shell\u02C3'); + } + if (!this.content) { + throw Error('[NullContentError] No content template decorated with `sciPopupShellContent` directive modelled as content child of \u02C2sci-popup-shell\u02C3'); + } + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/popup-shell/popup-shell.module.ts b/projects/e2e/workbench-application-platform/common/src/lib/popup-shell/popup-shell.module.ts new file mode 100644 index 000000000..9f5f72490 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/popup-shell/popup-shell.module.ts @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SciPopupShellComponent } from './popup-shell.component'; +import { SciPopupShellTitleDirective } from './popup-shell-title.directive'; +import { SciPopupShellContentDirective } from './popup-shell-content.directive'; +import { SciPopupShellButtonDirective } from './popup-shell-button.directive'; +import { SciViewportModule } from '@scion/viewport'; + +/** + * Provides the shell for a popup. + */ +@NgModule({ + declarations: [ + SciPopupShellComponent, + SciPopupShellTitleDirective, + SciPopupShellContentDirective, + SciPopupShellButtonDirective, + ], + imports: [ + CommonModule, + SciViewportModule, + ], + exports: [ + SciPopupShellComponent, + SciPopupShellTitleDirective, + SciPopupShellContentDirective, + SciPopupShellButtonDirective, + ], +}) +export class SciPopupShellModule { +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/property/property.component.html b/projects/e2e/workbench-application-platform/common/src/lib/property/property.component.html new file mode 100644 index 000000000..dbe7eaf1a --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/property/property.component.html @@ -0,0 +1,4 @@ + + {{property.key}}: + {{property.value}} + diff --git a/projects/e2e/workbench-application-platform/common/src/lib/property/property.component.scss b/projects/e2e/workbench-application-platform/common/src/lib/property/property.component.scss new file mode 100644 index 000000000..519c3690e --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/property/property.component.scss @@ -0,0 +1,17 @@ +:host { + display: grid; + grid-template-columns: max-content 3fr; + grid-column-gap: 1em; + grid-row-gap: .5em; + grid-template-rows: auto; + + > span.key { + overflow: hidden; + text-overflow: ellipsis; + } + + > span.value { + overflow: hidden; + text-overflow: ellipsis; + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/property/property.component.ts b/projects/e2e/workbench-application-platform/common/src/lib/property/property.component.ts new file mode 100644 index 000000000..194a05a02 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/property/property.component.ts @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { KeyValue } from '@angular/common'; + +/** + * Show the properties of an object. + */ +@Component({ + selector: 'sci-property', + templateUrl: './property.component.html', + styleUrls: ['./property.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SciPropertyComponent { + + public flattenedProperties: { [key: string]: any }; + private _keys: string[]; + + @Input() + public set properties(properties: any) { + this.flattenedProperties = this.flattenObject(properties || {}); + this._keys = Object.keys(this.flattenedProperties || {}); + } + + /** + * Compares qualifier entries by their position in the object. + */ + public keyCompareFn = (a: KeyValue, b: KeyValue): number => { + return this._keys.indexOf(a.key) - this._keys.indexOf(b.key); + }; + + private flattenObject(property: any, path: string[] = []): { [key: string]: any } { + return Object.keys(property).reduce((acc, key) => { + const value = property[key]; + if (typeof value === 'object') { + return {...acc, ...this.flattenObject(value, [...path, key])}; + } + else { + const propName = [...path, key].join('.'); + return {...acc, ...{[propName]: value}}; + } + }, {}); + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/property/property.module.ts b/projects/e2e/workbench-application-platform/common/src/lib/property/property.module.ts new file mode 100644 index 000000000..1cf628e26 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/property/property.module.ts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SciPropertyComponent } from './property.component'; + +/** + * Allows to show the properties of an object. + */ +@NgModule({ + declarations: [ + SciPropertyComponent, + ], + exports: [ + SciPropertyComponent, + ], + imports: [ + CommonModule, + ] +}) +export class SciPropertyModule { +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/sash/sash.directive.ts b/projects/e2e/workbench-application-platform/common/src/lib/sash/sash.directive.ts new file mode 100644 index 000000000..a0d2e1453 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/sash/sash.directive.ts @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Directive, EventEmitter, HostBinding, HostListener, Inject, Input, OnChanges, OnDestroy, Output, SimpleChanges } from '@angular/core'; +import { fromEvent, Subject } from 'rxjs'; +import { DOCUMENT } from '@angular/common'; +import { first, takeUntil } from 'rxjs/operators'; + +/** + * Allows the host element to be used as splitter in a sash box. + * + * Emits delta pixels when the host element is moved. + * + */ +@Directive({ + selector: '[sciSash]' +}) +export class SciSashDirective implements OnDestroy, OnChanges { + + private _destroy$ = new Subject(); + private _mousePosition: number; + + @Input('sciSash') // tslint:disable-line:no-input-rename + public direction: 'vertical' | 'horizontal'; + + @Output('sciSashStart') // tslint:disable-line:no-output-rename + public sashStart = new EventEmitter(); + + @Output('sciSashChange') // tslint:disable-line:no-output-rename + public sashChange = new EventEmitter(); + + @Output('sciSashEnd') // tslint:disable-line:no-output-rename + public sashEnd = new EventEmitter(); + + @Output('sciSashReset') // tslint:disable-line:no-output-rename + public sashReset = new EventEmitter(); + + @HostBinding('style.cursor') + public cursor: string; + + constructor(@Inject(DOCUMENT) private _document: any) { + } + + public ngOnChanges(changes: SimpleChanges): void { + this.cursor = this.direction === 'vertical' ? 'ew-resize' : 'ns-resize'; + } + + @HostListener('dblclick') + public onDoubleClick(): void { + this.sashReset.emit(); + } + + @HostListener('mousedown', ['$event']) + public onMouseDown(event: MouseEvent): void { + if (event.button !== 0) { + return; + } + + event.preventDefault(); + this._mousePosition = this.extractMousePosition(event); + + // Apply cursor on document level to prevent flickering while sashing + const oldCursor = this._document.body.style.cursor; + this._document.body.style.cursor = this.cursor; + + // Listen for 'mousemove' events + const mousemoveListener = fromEvent(this._document, 'mousemove') + .pipe( + takeUntil(this._destroy$) + ) + .subscribe((mousemoveEvent: MouseEvent) => { + mousemoveEvent.preventDefault(); + const mousePosition = this.extractMousePosition(mousemoveEvent); + const delta = mousePosition - this._mousePosition; + this._mousePosition = mousePosition; + this.sashChange.emit(delta); + }); + + // Listen for 'mouseup' events + fromEvent(this._document, 'mouseup') + .pipe( + first(), + takeUntil(this._destroy$) + ) + .subscribe(() => { + mousemoveListener.unsubscribe(); + this._mousePosition = null; + this._document.body.style.cursor = oldCursor; + this.sashEnd.next(); + }); + + this.sashStart.next(); + } + + public extractMousePosition(event: MouseEvent): number { + return this.direction === 'vertical' ? event.pageX : event.pageY; + } + + public ngOnDestroy(): void { + this._destroy$.next(); + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/sash/sash.module.ts b/projects/e2e/workbench-application-platform/common/src/lib/sash/sash.module.ts new file mode 100644 index 000000000..77f18b5fb --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/sash/sash.module.ts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SciSashDirective } from './sash.directive'; + +/** + * Allows the host element to be used as splitter in a sash box. + */ +@NgModule({ + declarations: [ + SciSashDirective, + ], + imports: [ + CommonModule, + ], + exports: [ + SciSashDirective, + ], +}) +export class SciSashModule { +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/session-storage/session-storage.module.ts b/projects/e2e/workbench-application-platform/common/src/lib/session-storage/session-storage.module.ts new file mode 100644 index 000000000..344d01dd9 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/session-storage/session-storage.module.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; + +/** + * Provides {SciSessionStorageService} to interact with session storage. + * + * Session storage maintains a separate storage area per origin that is available for the duration of the page session + * (as long as the browser is open, including page reloads and restores). + */ +@NgModule({}) +export class SciSessionStorageModule { +} diff --git a/projects/e2e/workbench-application-platform/common/src/lib/session-storage/session-storage.service.ts b/projects/e2e/workbench-application-platform/common/src/lib/session-storage/session-storage.service.ts new file mode 100644 index 000000000..269866383 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/session-storage/session-storage.service.ts @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Injectable } from '@angular/core'; +import { fromEvent, merge, NEVER, Observable, of, Subject } from 'rxjs'; +import { filter, map, startWith, switchMap, tap } from 'rxjs/operators'; + +/** + * Indicates data being loaded. + */ +const LOADING_HINT = 'LOADING_HINT [SessionStorageService]'; + +/** + * Allows to interact with session storage. + * + * Session storage maintains a separate storage area per origin that is available for the duration of the page session + * (as long as the browser is open, including page reloads and restores). + */ +@Injectable({providedIn: 'root'}) +export class SciSessionStorageService { + + private _currentDocumentChange$ = new Subject(); + + /** + * Puts data into session storage. + */ + public put(key: string, value: any): void { + if (value === LOADING_HINT) { + throw Error(`[IllegalValueError] Reserved value used by \`SessionStorageService\` [value=${LOADING_HINT}]`); + } + + sessionStorage.setItem(key, JSON.stringify(value)); + this._currentDocumentChange$.next(key); + } + + /** + * Returns data of given key from session storage. + * + * Upon subscription, it emits latest data, if any, and then continuously emits when data change. It never completes. + * + * Optionally, you can provide a supplier to put data into session storage if missing. + */ + public observe$(key: string, supplierIfAbsentFn$?: () => Observable): Observable { + const otherDocumentChange$ = fromEvent(window, 'storage') + .pipe( + filter(event => event.storageArea === sessionStorage), + map(event => event.key), + ); + + return merge(this._currentDocumentChange$, otherDocumentChange$) + .pipe( + filter(itemKey => itemKey === key), + startWith(key), + map(itemKey => sessionStorage.getItem(itemKey)), + filter(item => !item || item !== LOADING_HINT), + map(item => item && JSON.parse(item)), + switchMap((item: T | undefined): Observable => { + if (item) { + return of(item); + } + + if (!supplierIfAbsentFn$) { + return NEVER; + } + + sessionStorage.setItem(key, LOADING_HINT); + return supplierIfAbsentFn$() + .pipe( + tap(it => this.put(key, it)), + switchMap(it => of(it)) + ); + }) + ); + } +} + diff --git a/projects/e2e/workbench-application-platform/common/src/lib/uuid/uuid.util.ts b/projects/e2e/workbench-application-platform/common/src/lib/uuid/uuid.util.ts new file mode 100644 index 000000000..0477a61d3 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/lib/uuid/uuid.util.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/** + * Utility for random numbers. + */ +export class UUID { + + private constructor() { + } + + /** + * Generates a 'pseudo-random' identifier. + */ + public static randomUUID(): string { + let now = Date.now(); + if (window.performance && typeof window.performance.now === 'function') { + now += performance.now(); // use high-precision timer if available + } + + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, char => { + const random = (now + Math.random() * 16) % 16 | 0; // tslint:disable-line:no-bitwise + now = Math.floor(now / 16); + return (char === 'x' ? random : (random & 0x3 | 0x8)).toString(16); // tslint:disable-line:no-bitwise + }); + } +} diff --git a/projects/e2e/workbench-application-platform/common/src/public_api.ts b/projects/e2e/workbench-application-platform/common/src/public_api.ts new file mode 100644 index 000000000..efd8612f5 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/src/public_api.ts @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/** + * Public API of `SciAccordionModule` + */ +export { SciAccordionModule } from './lib/accordion/accordion.module'; +export { SciAccordionComponent } from './lib/accordion/accordion.component'; +export { SciAccordionItemDirective } from './lib/accordion/accordion-item.directive'; + +/** + * Public API of `SciPopupShellModule` + */ +export { SciPopupShellModule } from './lib/popup-shell/popup-shell.module'; +export { SciPopupShellComponent } from './lib/popup-shell/popup-shell.component'; +export { SciPopupShellTitleDirective } from './lib/popup-shell/popup-shell-title.directive'; +export { SciPopupShellContentDirective } from './lib/popup-shell/popup-shell-content.directive'; +export { SciPopupShellButtonDirective } from './lib/popup-shell/popup-shell-button.directive'; + +/** + * Public API of `SciSessionStorageModule` + */ +export { SciSessionStorageModule } from './lib/session-storage/session-storage.module'; +export { SciSessionStorageService } from './lib/session-storage/session-storage.service'; + +/** + * Public API of `SciFilterFieldModule` + */ +export { SciFilterFieldModule } from './lib/filter-field/filter-field.module'; +export { SciFilterFieldComponent, toFilterRegExp } from './lib/filter-field/filter-field.component'; + +/** + * Public API of `uuid` + */ +export { UUID } from './lib/uuid/uuid.util'; + +/** + * Public API of `SciListModule` + */ +export { SciListModule } from './lib/list/list.module'; +export { SciListComponent } from './lib/list/list.component'; +export { SciListItemDirective } from './lib/list/list-item.directive'; +export { SciListStyle } from './lib/list/metadata'; + +/** + * Public API of `SciSashModule` + */ +export { SciSashModule } from './lib/sash/sash.module'; +export { SciSashDirective } from './lib/sash/sash.directive'; + +/** + * Public API of `SciParamsEnterModule` + */ +export { SciParamsEnterModule } from './lib/params-enter/params-enter.module'; +export { SciParamsEnterComponent, PARAM_VALUE, PARAM_NAME } from './lib/params-enter/params-enter.component'; + +/** + * Public API of `SciPropertyModule` + */ +export { SciPropertyModule } from './lib/property/property.module'; +export { SciPropertyComponent } from './lib/property/property.component'; + +/** + * Public API of `custom-extension` + */ +export * from './lib/custom-extension/metadata'; diff --git a/projects/e2e/workbench-application-platform/common/tsconfig.lib.json b/projects/e2e/workbench-application-platform/common/tsconfig.lib.json new file mode 100644 index 000000000..3d57476f9 --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/tsconfig.lib.json @@ -0,0 +1,28 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../../out-tsc/lib", + "target": "es2015", + "module": "es2015", + "moduleResolution": "node", + "declaration": true, + "sourceMap": true, + "inlineSources": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "types": [], + "lib": [ + "dom", + "es2018" + ] + }, + "angularCompilerOptions": { + "annotateForClosureCompiler": true, + "skipTemplateCodegen": true, + "strictMetadataEmit": true, + "fullTemplateTypeCheck": true, + "strictInjectionParameters": true, + "enableResourceInlining": true + } +} diff --git a/projects/e2e/workbench-application-platform/common/tslint.json b/projects/e2e/workbench-application-platform/common/tslint.json new file mode 100644 index 000000000..b5ac9f50b --- /dev/null +++ b/projects/e2e/workbench-application-platform/common/tslint.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../../tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + [ + "sci" + ], + "camelCase" + ], + "component-selector": [ + true, + "element", + [ + "sci" + ], + "kebab-case" + ] + } +} diff --git a/projects/e2e/workbench-application-platform/communication-app/browserslist b/projects/e2e/workbench-application-platform/communication-app/browserslist new file mode 100644 index 000000000..37371cb04 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/browserslist @@ -0,0 +1,11 @@ +# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed + +> 0.5% +last 2 versions +Firefox ESR +not dead +not IE 9-11 \ No newline at end of file diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/app-routing.module.ts b/projects/e2e/workbench-application-platform/communication-app/src/app/app-routing.module.ts new file mode 100644 index 000000000..793ca848c --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/app-routing.module.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + {path: 'communication', loadChildren: './communication/communication.module#CommunicationModule'}, +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes, {useHash: true})], + exports: [RouterModule] +}) +export class AppRoutingModule { +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/app.component.html b/projects/e2e/workbench-application-platform/communication-app/src/app/app.component.html new file mode 100644 index 000000000..8fe45de6e --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/app.component.html @@ -0,0 +1,3 @@ + + + diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/app.component.scss b/projects/e2e/workbench-application-platform/communication-app/src/app/app.component.scss new file mode 100644 index 000000000..e1f2859cf --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/app.component.scss @@ -0,0 +1,13 @@ +@import 'common'; + +:host { + @include position(absolute, 0, 0, 0, 0); + + > sci-viewport { + @include position(absolute, 0, 0, 0, 0); + + router-outlet { + position: absolute; // take router outlet out of the document flow + } + } +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/app.component.ts b/projects/e2e/workbench-application-platform/communication-app/src/app/app.component.ts new file mode 100644 index 000000000..15f0590f2 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/app.component.ts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent { +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/app.module.ts b/projects/e2e/workbench-application-platform/communication-app/src/app/app.module.ts new file mode 100644 index 000000000..511d6e1d8 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/app.module.ts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; + +import { AppComponent } from './app.component'; +import { SciViewportModule } from '@scion/viewport'; +import { AppRoutingModule } from './app-routing.module'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { WorkbenchApplicationModule } from '@scion/workbench-application.angular'; + +@NgModule({ + declarations: [ + AppComponent, + ], + imports: [ + BrowserModule, + WorkbenchApplicationModule.forRoot(), + SciViewportModule, + AppRoutingModule, + BrowserAnimationsModule, + ], + providers: [], + bootstrap: [AppComponent] +}) +export class AppModule { +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-accordion-item-header/communication-accordion-item-header.component.html b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-accordion-item-header/communication-accordion-item-header.component.html new file mode 100644 index 000000000..c48515498 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-accordion-item-header/communication-accordion-item-header.component.html @@ -0,0 +1,10 @@ + + + + + + + + +{{communication.subject}} +{{communication.date | date:'d. MMM yyyy'}} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-accordion-item-header/communication-accordion-item-header.component.scss b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-accordion-item-header/communication-accordion-item-header.component.scss new file mode 100644 index 000000000..53bbd0cd4 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-accordion-item-header/communication-accordion-item-header.component.scss @@ -0,0 +1,18 @@ +:host { + display: flex; + + > span.icon { + flex: none; + width: 30px; + font-size: 20px; + } + + > span.subject { + flex: auto; + } + + > span.date { + flex: none; + font-size: .8em; + } +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-accordion-item-header/communication-accordion-item-header.component.ts b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-accordion-item-header/communication-accordion-item-header.component.ts new file mode 100644 index 000000000..e3857e54a --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-accordion-item-header/communication-accordion-item-header.component.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, Input } from '@angular/core'; +import { Communication } from '../communication.model'; + +@Component({ + selector: 'app-communication-accordion-item-header', + templateUrl: './communication-accordion-item-header.component.html', + styleUrls: ['./communication-accordion-item-header.component.scss'] +}) +export class CommunicationAccordionItemHeaderComponent { + + @Input() + public communication: Communication; +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-new-popup/communication-new-popup.component.html b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-new-popup/communication-new-popup.component.html new file mode 100644 index 000000000..d724f601f --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-new-popup/communication-new-popup.component.html @@ -0,0 +1,28 @@ + + New communication + + +
+ + + + + + + + + + + +
+
+ + OK +
diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-new-popup/communication-new-popup.component.scss b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-new-popup/communication-new-popup.component.scss new file mode 100644 index 000000000..0de4616d1 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-new-popup/communication-new-popup.component.scss @@ -0,0 +1,29 @@ +@import 'common'; + +:host { + display: flex; + flex-direction: column; + + > sci-popup-shell { + flex: auto; + + form { + flex: auto; + display: grid; + grid-template-columns: 100px auto; + grid-column-gap: 1em; + grid-row-gap: .5em; + grid-template-rows: auto; + @include grid-container-align-items(center); + + > label[for="message"] { + align-self: start; + } + + > textarea#message { + height: 16em; + resize: none; + } + } + } +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-new-popup/communication-new-popup.component.ts b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-new-popup/communication-new-popup.component.ts new file mode 100644 index 000000000..faed42549 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-new-popup/communication-new-popup.component.ts @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, OnDestroy } from '@angular/core'; +import { provideWorkbenchPopup, WorkbenchPopup } from '@scion/workbench-application.angular'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { noop, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { Communication } from '../communication.model'; +import { CommunicationService } from '../communication.service'; +import { ActivatedRoute } from '@angular/router'; +import { UUID } from '@scion/e2e/common'; + +const CHANNEL = 'channel'; +const DATE = 'date'; +const SUBJECT = 'subject'; +const MESSAGE = 'message'; + +@Component({ + selector: 'app-communication-new-popup', + templateUrl: './communication-new-popup.component.html', + styleUrls: ['./communication-new-popup.component.scss'], + providers: [ + provideWorkbenchPopup(CommunicationNewPopupComponent) + ] +}) +export class CommunicationNewPopupComponent implements OnDestroy { + + public readonly CHANNEL = CHANNEL; + public readonly DATE = DATE; + public readonly SUBJECT = SUBJECT; + public readonly MESSAGE = MESSAGE; + + private _destroy$ = new Subject(); + public form: FormGroup; + + constructor(private _popup: WorkbenchPopup, + private _communicationService: CommunicationService, + private _route: ActivatedRoute, + formBuilder: FormBuilder) { + this.form = formBuilder.group({ + [CHANNEL]: formBuilder.control('email', Validators.required), + [DATE]: formBuilder.control(nowAsIsoString(), Validators.required), + [SUBJECT]: formBuilder.control('', Validators.required), + [MESSAGE]: formBuilder.control('', Validators.required), + }); + } + + public onOk(): void { + const contactId = this._route.snapshot.params['contactId']; + + const communication: Communication = { + id: UUID.randomUUID(), + contactId: contactId, + channel: this.form.get(CHANNEL).value, + date: this.form.get(DATE).value, + subject: this.form.get(SUBJECT).value, + message: this.form.get(MESSAGE).value + }; + + this._communicationService.create$(communication) + .pipe(takeUntil(this._destroy$)) + .subscribe(noop, noop, () => this._popup.close()); + } + + public ngOnDestroy(): void { + this._destroy$.next(); + } +} + +function nowAsIsoString(): string { + const now = /(\d{4})-(\d{2})-(\d{2})/.exec(new Date().toISOString()); + return `${now[1]}-${now[2]}-${now[3]}`; +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-routing.module.ts b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-routing.module.ts new file mode 100644 index 000000000..5b2e32a0d --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-routing.module.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { CommunicationViewComponent } from './communication-view/communication-view.component'; +import { CommunicationNewPopupComponent } from './communication-new-popup/communication-new-popup.component'; + +const routes: Routes = [ + {path: '', component: CommunicationViewComponent}, + {path: 'new', component: CommunicationNewPopupComponent}, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class CommunicationRoutingModule { +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-view/communication-view.component.html b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-view/communication-view.component.html new file mode 100644 index 000000000..7b6ae1fc2 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-view/communication-view.component.html @@ -0,0 +1,10 @@ + + + + + + +
{{communication.message}}
+
+
+
diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-view/communication-view.component.scss b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-view/communication-view.component.scss new file mode 100644 index 000000000..b7a710cf3 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-view/communication-view.component.scss @@ -0,0 +1,17 @@ +@import 'common'; + +:host { + display: flex; + padding: app-padding(); + + > sci-accordion { + flex: auto; + + div.message { + font-size: .8em; + text-overflow: ellipsis; + overflow: hidden; + } + } +} + diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-view/communication-view.component.ts b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-view/communication-view.component.ts new file mode 100644 index 000000000..2dba37da0 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication-view/communication-view.component.ts @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, TrackByFunction } from '@angular/core'; +import { Communication } from '../communication.model'; +import { Observable } from 'rxjs'; +import { CommunicationService } from '../communication.service'; +import { ActivatedRoute } from '@angular/router'; +import { distinctUntilChanged, map, switchMap } from 'rxjs/operators'; +import { provideWorkbenchView, WorkbenchView } from '@scion/workbench-application.angular'; + +@Component({ + selector: 'app-communication-view', + templateUrl: './communication-view.component.html', + styleUrls: ['./communication-view.component.scss'], + providers: [ + provideWorkbenchView(CommunicationViewComponent) + ] +}) +export class CommunicationViewComponent { + + public communications$: Observable; + + constructor(communicationService: CommunicationService, + route: ActivatedRoute, + view: WorkbenchView) { + view.heading = 'Communications'; + view.title = route.snapshot.params['contactFullName']; + + this.communications$ = route.params + .pipe( + map(params => params['contactId']), + distinctUntilChanged(), + switchMap(contactId => communicationService.communicationsByContactId$(contactId)), + ); + } + + public trackByFn: TrackByFunction = (index: number, communication: Communication): any => { + return communication.id; + }; +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication.model.ts b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication.model.ts new file mode 100644 index 000000000..c9d6a852b --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication.model.ts @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +export interface Communication { + id: string; + contactId: string; + channel: 'email' | 'letter' | 'phone' | 'sms' | 'facebook' | 'twitter'; + date: string; + subject: string; + message: string; +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication.module.ts b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication.module.ts new file mode 100644 index 000000000..0211d66c9 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication.module.ts @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SciViewportModule } from '@scion/viewport'; +import { SciAccordionModule, SciPopupShellModule, SciSessionStorageModule } from '@scion/e2e/common'; +import { CommunicationViewComponent } from './communication-view/communication-view.component'; +import { CommunicationAccordionItemHeaderComponent } from './communication-accordion-item-header/communication-accordion-item-header.component'; +import { CommunicationRoutingModule } from './communication-routing.module'; +import { CommunicationNewPopupComponent } from './communication-new-popup/communication-new-popup.component'; +import { ReactiveFormsModule } from '@angular/forms'; +import { WorkbenchApplicationModule } from '@scion/workbench-application.angular'; + +@NgModule({ + declarations: [ + CommunicationViewComponent, + CommunicationNewPopupComponent, + CommunicationAccordionItemHeaderComponent, + ], + imports: [ + CommonModule, + SciViewportModule, + CommunicationRoutingModule, + SciAccordionModule, + SciPopupShellModule, + SciSessionStorageModule, + ReactiveFormsModule, + WorkbenchApplicationModule.forChild(), + ], + exports: [], +}) +export class CommunicationModule { +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication.service.ts b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication.service.ts new file mode 100644 index 000000000..158b01039 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/app/communication/communication.service.ts @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Injectable, OnDestroy } from '@angular/core'; +import { map, mergeMapTo, take, tap } from 'rxjs/operators'; +import { EMPTY, MonoTypeOperatorFunction, Observable, of, OperatorFunction, Subject } from 'rxjs'; +import { SciSessionStorageService } from '@scion/e2e/common'; +import { Communication } from './communication.model'; + +const COMMUNICATIONS_STORAGE_KEY = 'communication.data'; + +@Injectable({providedIn: 'root'}) +export class CommunicationService implements OnDestroy { + + private _destroy$ = new Subject(); + private _communications$: Observable; + + constructor(private _storage: SciSessionStorageService) { + this._communications$ = this._storage.observe$(COMMUNICATIONS_STORAGE_KEY, () => of([])); + } + + public communicationsByContactId$(contactId: string): Observable { + return this._communications$.pipe(filterByContactId(contactId)); + } + + public create$(communication: Communication): Observable { + return this._communications$ + .pipe( + once(), + addCommunication(this._storage, communication), + mergeMapTo(EMPTY), + ); + } + + public ngOnDestroy(): void { + this._destroy$.next(); + } +} + +function filterByContactId(contactId: string): OperatorFunction { + return map((communications: Communication[]): Communication[] => { + return communications + .filter(communication => communication.contactId === contactId) + .sort((c1, c2) => c1.date.localeCompare(c2.date)); + }); +} + +function addCommunication(storage: SciSessionStorageService, communication: Communication): MonoTypeOperatorFunction { + return tap((communications: Communication[]): void => { + communications.push(communication); + storage.put(COMMUNICATIONS_STORAGE_KEY, communications); + }); +} + +function once(): MonoTypeOperatorFunction { + return take(1); +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/assets/.gitkeep b/projects/e2e/workbench-application-platform/communication-app/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/projects/e2e/workbench-application-platform/communication-app/src/assets/manifest.json b/projects/e2e/workbench-application-platform/communication-app/src/assets/manifest.json new file mode 100644 index 000000000..4a2afca2e --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/assets/manifest.json @@ -0,0 +1,45 @@ +{ + "name": "Communication Application", + "baseUrl": "#", + "capabilities": [ + { + "type": "view", + "qualifier": { + "entity": "communication", + "presentation": "list", + "contactId": "*" + }, + "private": false, + "description": "Lists all communications of a contact.", + "properties": { + "path": "communication", + "matrixParams": { + "contactId": ":contactId" + }, + "title": "Communications", + "cssClass": "e2e-communication-list" + } + }, + { + "type": "popup", + "qualifier": { + "entity": "communication", + "action": "create", + "contactId": "*" + }, + "private": false, + "description": "Allows to create a new communication for a contact.", + "properties": { + "path": "communication/new", + "matrixParams": { + "contactId": ":contactId" + }, + "width": "500px", + "height": "520px", + "cssClass": "e2e-communication-create" + } + } + ], + "intents": [ + ] +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/environments/environment.now.ts b/projects/e2e/workbench-application-platform/communication-app/src/environments/environment.now.ts new file mode 100644 index 000000000..dc39aca58 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/environments/environment.now.ts @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +export const environment = { + production: true +}; diff --git a/projects/e2e/workbench-application-platform/communication-app/src/environments/environment.ts b/projects/e2e/workbench-application-platform/communication-app/src/environments/environment.ts new file mode 100644 index 000000000..2a785475f --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/environments/environment.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --configuration=now` replaces `environment.ts` with `environment.now.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/projects/e2e/workbench-application-platform/communication-app/src/index.html b/projects/e2e/workbench-application-platform/communication-app/src/index.html new file mode 100644 index 000000000..344beb469 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/index.html @@ -0,0 +1,13 @@ + + + + + Communication Application + + + + + + + + diff --git a/projects/e2e/workbench-application-platform/communication-app/src/main.ts b/projects/e2e/workbench-application-platform/communication-app/src/main.ts new file mode 100644 index 000000000..87c3f3d9a --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/main.ts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/projects/e2e/workbench-application-platform/communication-app/src/now.json b/projects/e2e/workbench-application-platform/communication-app/src/now.json new file mode 100644 index 000000000..dc73c6b63 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/now.json @@ -0,0 +1,5 @@ +{ + "version": 2, + "name": "scion-workbench-application-platform-communication", + "alias": "scion-workbench-application-platform-communication" +} diff --git a/projects/e2e/workbench-application-platform/communication-app/src/polyfills.ts b/projects/e2e/workbench-application-platform/communication-app/src/polyfills.ts new file mode 100644 index 000000000..f56cc3de6 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/polyfills.ts @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +import 'core-js/es6/symbol'; +import 'core-js/es6/object'; +import 'core-js/es6/function'; +import 'core-js/es6/parse-int'; +import 'core-js/es6/parse-float'; +import 'core-js/es6/number'; +import 'core-js/es6/math'; +import 'core-js/es6/string'; +import 'core-js/es6/date'; +import 'core-js/es6/array'; +import 'core-js/es6/regexp'; +import 'core-js/es6/map'; +import 'core-js/es6/weak-map'; +import 'core-js/es6/set'; +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + +/** + * If the application will be indexed by Google Search, the following is required. + * Googlebot uses a renderer based on Chrome 41. + * https://developers.google.com/search/docs/guides/rendering + **/ +// import 'core-js/es6/array'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following for the Reflect API. */ +// import 'core-js/es6/reflect'; + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + */ + + // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + + /* + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + */ +// (window as any).__Zone_enable_cross_context_check = true; + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/projects/e2e/workbench-application-platform/communication-app/src/styles.scss b/projects/e2e/workbench-application-platform/communication-app/src/styles.scss new file mode 100644 index 000000000..1506f1627 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/src/styles.scss @@ -0,0 +1,3 @@ +@import 'common'; + +@include app-theme(); diff --git a/projects/e2e/workbench-application-platform/communication-app/tsconfig.app.json b/projects/e2e/workbench-application-platform/communication-app/tsconfig.app.json new file mode 100644 index 000000000..27e4b91c8 --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/tsconfig.app.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../../out-tsc/communication-app", + "types": [] + } +} diff --git a/projects/e2e/workbench-application-platform/communication-app/tslint.json b/projects/e2e/workbench-application-platform/communication-app/tslint.json new file mode 100644 index 000000000..78a62a0db --- /dev/null +++ b/projects/e2e/workbench-application-platform/communication-app/tslint.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ] + } +} diff --git a/projects/e2e/workbench-application-platform/contact-app/browserslist b/projects/e2e/workbench-application-platform/contact-app/browserslist new file mode 100644 index 000000000..37371cb04 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/browserslist @@ -0,0 +1,11 @@ +# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed + +> 0.5% +last 2 versions +Firefox ESR +not dead +not IE 9-11 \ No newline at end of file diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/app-routing.module.ts b/projects/e2e/workbench-application-platform/contact-app/src/app/app-routing.module.ts new file mode 100644 index 000000000..f32749bea --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/app-routing.module.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + {path: 'contact', loadChildren: './contact/contact.module#ContactModule'}, +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes, {useHash: true})], + exports: [RouterModule] +}) +export class AppRoutingModule { +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/app.component.html b/projects/e2e/workbench-application-platform/contact-app/src/app/app.component.html new file mode 100644 index 000000000..8fe45de6e --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/app.component.html @@ -0,0 +1,3 @@ + + + diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/app.component.scss b/projects/e2e/workbench-application-platform/contact-app/src/app/app.component.scss new file mode 100644 index 000000000..e1f2859cf --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/app.component.scss @@ -0,0 +1,13 @@ +@import 'common'; + +:host { + @include position(absolute, 0, 0, 0, 0); + + > sci-viewport { + @include position(absolute, 0, 0, 0, 0); + + router-outlet { + position: absolute; // take router outlet out of the document flow + } + } +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/app.component.ts b/projects/e2e/workbench-application-platform/contact-app/src/app/app.component.ts new file mode 100644 index 000000000..15f0590f2 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/app.component.ts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent { +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/app.module.ts b/projects/e2e/workbench-application-platform/contact-app/src/app/app.module.ts new file mode 100644 index 000000000..49f5b608e --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/app.module.ts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; + +import { AppComponent } from './app.component'; +import { WorkbenchApplicationModule } from '@scion/workbench-application.angular'; +import { SciViewportModule } from '@scion/viewport'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { AppRoutingModule } from './app-routing.module'; + +@NgModule({ + declarations: [ + AppComponent, + ], + imports: [ + BrowserModule, + WorkbenchApplicationModule.forRoot(), + SciViewportModule, + AppRoutingModule, + BrowserAnimationsModule, + ], + providers: [], + bootstrap: [ + AppComponent, + ] +}) +export class AppModule { +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-activity/contact-activity.component.html b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-activity/contact-activity.component.html new file mode 100644 index 000000000..d7b21f351 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-activity/contact-activity.component.html @@ -0,0 +1,19 @@ + + + + + {{contact.firstname}} {{contact.lastname}}, {{contact.city}} + + + + + + + + + + diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-activity/contact-activity.component.scss b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-activity/contact-activity.component.scss new file mode 100644 index 000000000..57bda86d3 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-activity/contact-activity.component.scss @@ -0,0 +1,10 @@ +@import 'common'; + +:host { + display: flex; + padding: app-padding(); + + > sci-list { + flex: auto; + } +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-activity/contact-activity.component.ts b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-activity/contact-activity.component.ts new file mode 100644 index 000000000..5f43adbda --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-activity/contact-activity.component.ts @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, OnDestroy, TrackByFunction } from '@angular/core'; +import { provideWorkbenchActivity } from '@scion/workbench-application.angular'; +import { takeUntil } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; +import { ContactService, filterContacts } from '../contact.service'; +import { Contact } from '../contact.model'; + +@Component({ + selector: 'app-contact-activity', + templateUrl: './contact-activity.component.html', + styleUrls: ['./contact-activity.component.scss'], + providers: [ + provideWorkbenchActivity(ContactActivityComponent) + ] +}) +export class ContactActivityComponent implements OnDestroy { + + private _destroy$ = new Subject(); + private _filter$ = new BehaviorSubject(null); + + public contacts$: Observable; + + constructor(private _contactService: ContactService) { + this.contacts$ = combineLatest(this._filter$, this._contactService.contacts$()).pipe(filterContacts()); + } + + public onDelete(contactId: string): void { + this._contactService.delete$(contactId) + .pipe(takeUntil(this._destroy$)) + .subscribe(); + } + + public onFilter(filterText: string): void { + this._filter$.next(filterText); + } + + public ngOnDestroy(): void { + this._destroy$.next(); + } + + public trackByFn: TrackByFunction = (index: number, contact: Contact): any => { + return contact.id; + }; +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-new-popup/contact-new-popup.component.html b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-new-popup/contact-new-popup.component.html new file mode 100644 index 000000000..76dd1208f --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-new-popup/contact-new-popup.component.html @@ -0,0 +1,27 @@ + + New contact + + +
+ + + + + + + + + + + + + + + + + +
+
+ + OK +
diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-new-popup/contact-new-popup.component.scss b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-new-popup/contact-new-popup.component.scss new file mode 100644 index 000000000..f4d4e72c1 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-new-popup/contact-new-popup.component.scss @@ -0,0 +1,19 @@ +@import 'common'; + +:host { + display: flex; + flex-direction: column; + + > sci-popup-shell { + flex: auto; + + form { + flex: auto; + display: grid; + grid-template-columns: 100px auto; + grid-column-gap: 1em; + grid-row-gap: .5em; + @include grid-container-align-items(center); + } + } +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-new-popup/contact-new-popup.component.ts b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-new-popup/contact-new-popup.component.ts new file mode 100644 index 000000000..bfe85e0a3 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-new-popup/contact-new-popup.component.ts @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, OnDestroy } from '@angular/core'; +import { provideWorkbenchPopup, WorkbenchPopup, WorkbenchRouter } from '@scion/workbench-application.angular'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { ContactService } from '../contact.service'; +import { UUID } from '@scion/e2e/common'; +import { noop, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { Contact } from '../contact.model'; + +const FIRSTNAME = 'firstname'; +const LASTNAME = 'lastname'; +const STREET = 'street'; +const CITY = 'city'; +const EMAIL = 'email'; +const PHONE = 'phone'; + +@Component({ + selector: 'app-contact-new-popup', + templateUrl: './contact-new-popup.component.html', + styleUrls: ['./contact-new-popup.component.scss'], + providers: [ + provideWorkbenchPopup(ContactNewPopupComponent) + ] +}) +export class ContactNewPopupComponent implements OnDestroy { + + public readonly FIRSTNAME = FIRSTNAME; + public readonly LASTNAME = LASTNAME; + public readonly STREET = STREET; + public readonly CITY = CITY; + public readonly EMAIL = EMAIL; + public readonly PHONE = PHONE; + + private _destroy$ = new Subject(); + public form: FormGroup; + + constructor(private _popup: WorkbenchPopup, + private _contactService: ContactService, + private _router: WorkbenchRouter, + formBuilder: FormBuilder) { + this.form = formBuilder.group({ + [FIRSTNAME]: formBuilder.control('', Validators.required), + [LASTNAME]: formBuilder.control('', Validators.required), + [STREET]: formBuilder.control('', Validators.required), + [CITY]: formBuilder.control('', Validators.required), + [EMAIL]: formBuilder.control('', Validators.email), + [PHONE]: formBuilder.control(''), + }); + } + + public onOk(): void { + const contact: Contact = { + id: UUID.randomUUID(), + firstname: this.form.get(FIRSTNAME).value, + lastname: this.form.get(LASTNAME).value, + street: this.form.get(STREET).value, + city: this.form.get(CITY).value, + email: this.form.get(EMAIL).value, + phone: this.form.get(PHONE).value, + relatedContactIds: [], + }; + + this._contactService.create$(contact) + .pipe(takeUntil(this._destroy$)) + .subscribe(noop, noop, () => { + this._router.navigate({'entity': 'contact', 'id': contact.id}); + this._popup.close(); + }); + } + + public ngOnDestroy(): void { + this._destroy$.next(); + } +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-routing.module.ts b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-routing.module.ts new file mode 100644 index 000000000..a0bbce730 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-routing.module.ts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { ContactActivityComponent } from './contact-activity/contact-activity.component'; +import { ContactNewPopupComponent } from './contact-new-popup/contact-new-popup.component'; +import { ContactViewComponent } from './contact-view/contact-view.component'; +import { RelatedContactAddPopupComponent } from './related-contact-add-popup/related-contact-add-popup.component'; + +const routes: Routes = [ + {path: 'list', component: ContactActivityComponent}, + {path: 'new', component: ContactNewPopupComponent}, + {path: ':id', component: ContactViewComponent}, + {path: ':id/add-related-contact', component: RelatedContactAddPopupComponent}, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class ContactRoutingModule { +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-view/contact-view.component.html b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-view/contact-view.component.html new file mode 100644 index 000000000..7a7fef52f --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-view/contact-view.component.html @@ -0,0 +1,46 @@ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+

Related contacts

+ +
+ + + + + + {{relatedContact.firstname}} {{relatedContact.lastname}}, {{relatedContact.city}} + + + + + + + diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-view/contact-view.component.scss b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-view/contact-view.component.scss new file mode 100644 index 000000000..76b6ce118 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-view/contact-view.component.scss @@ -0,0 +1,48 @@ +@import 'common'; + +:host { + display: flex; + flex-direction: column; + padding: app-padding(); + + > form { + flex: none; + display: grid; + grid-template-columns: minmax(80px, 120px) auto; + grid-column-gap: 1em; + grid-row-gap: .5em; + grid-template-rows: auto; + + border: 1px solid accentColor(.25); + border-radius: 5px; + padding: 1em; + @include grid-container-align-items(center); + + > nav.communications { + grid-column: 1/3; + display: flex; + flex-direction: column; + align-items: flex-start; + + > * { + margin: 0.5em 0; + } + } + } + + > header { + flex: none; + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 2em; + + > h2 { + font-size: 1.1em; + } + } + + > sci-list.related-contacts { + flex: auto; + } +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-view/contact-view.component.ts b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-view/contact-view.component.ts new file mode 100644 index 000000000..fb8f5b477 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact-view/contact-view.component.ts @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, OnDestroy, TrackByFunction } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; +import { distinctUntilChanged, filter, map, switchMap, takeUntil, tap } from 'rxjs/operators'; +import { ContactService, filterContacts } from '../contact.service'; +import { provideWorkbenchView, WorkbenchRouter, WorkbenchView } from '@scion/workbench-application.angular'; +import { Popup, PopupService } from '@scion/workbench-application.core'; +import { Contact } from '../contact.model'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +const FIRSTNAME = 'firstname'; +const LASTNAME = 'lastname'; +const STREET = 'street'; +const CITY = 'city'; +const EMAIL = 'email'; +const PHONE = 'phone'; +const RELATED_PERSON_IDS = 'related-contact-ids'; + +@Component({ + selector: 'app-contact-view', + templateUrl: './contact-view.component.html', + styleUrls: ['./contact-view.component.scss'], + providers: [ + provideWorkbenchView(ContactViewComponent) + ] +}) +export class ContactViewComponent implements OnDestroy { + + public readonly FIRSTNAME = FIRSTNAME; + public readonly LASTNAME = LASTNAME; + public readonly STREET = STREET; + public readonly CITY = CITY; + public readonly EMAIL = EMAIL; + public readonly PHONE = PHONE; + + private _destroy$ = new Subject(); + private _relatedContactFilter$ = new BehaviorSubject(null); + + public form: FormGroup; + public contact: Contact; + public relatedContacts$: Observable; + + constructor(route: ActivatedRoute, + private _contactService: ContactService, + private _view: WorkbenchView, + private _router: WorkbenchRouter, + formBuilder: FormBuilder, + private _popupService: PopupService) { + this._view.heading = 'Contact'; + this.form = new FormGroup({ + [FIRSTNAME]: formBuilder.control('', Validators.required), + [LASTNAME]: formBuilder.control('', Validators.required), + [STREET]: formBuilder.control('', Validators.required), + [CITY]: formBuilder.control('', Validators.required), + [EMAIL]: formBuilder.control('', Validators.email), + [PHONE]: formBuilder.control(''), + [RELATED_PERSON_IDS]: formBuilder.control([]), + }); + + route.params + .pipe( + map(params => params['id']), + distinctUntilChanged(), + switchMap(id => this.load$(id)), + takeUntil(this._destroy$), + ) + .subscribe(); + + this.form.statusChanges + .pipe( + filter(() => this.form.valid), + switchMap(() => this.store$()), + takeUntil(this._destroy$), + ) + .subscribe(); + } + + private load$(contactId: string): Observable { + return this._contactService.contact$(contactId).pipe(tap((contact: Contact) => { + this.contact = contact; + this._view.title = `${this.contact.firstname} ${this.contact.lastname}`; + this.form.controls[FIRSTNAME].setValue(contact.firstname, {emitEvent: false}); + this.form.controls[LASTNAME].setValue(contact.lastname, {emitEvent: false}); + this.form.controls[STREET].setValue(contact.street, {emitEvent: false}); + this.form.controls[CITY].setValue(contact.city, {emitEvent: false}); + this.form.controls[EMAIL].setValue(contact.email, {emitEvent: false}); + this.form.controls[PHONE].setValue(contact.phone, {emitEvent: false}); + this.form.controls[RELATED_PERSON_IDS].setValue(contact.relatedContactIds, {emitEvent: false}); + this.relatedContacts$ = combineLatest(this._relatedContactFilter$, this._contactService.contacts$(contact.relatedContactIds)).pipe(filterContacts()); + }) + ); + } + + private store$(): Observable { + return this._contactService.update$({ + id: this.contact.id, + firstname: this.form.controls[FIRSTNAME].value, + lastname: this.form.controls[LASTNAME].value, + street: this.form.controls[STREET].value, + city: this.form.controls[CITY].value, + email: this.form.controls[EMAIL].value, + phone: this.form.controls[PHONE].value, + relatedContactIds: this.form.controls[RELATED_PERSON_IDS].value, + }); + } + + public onRelatedContactsFilter(filterText: string): void { + this._relatedContactFilter$.next(filterText); + } + + public onRelatedContactAdd(event: MouseEvent): void { + event.preventDefault(); + const popup: Popup = { + position: 'west', + anchor: event.target as Element, + }; + this._popupService.open(popup, { + 'entity': 'contact', + 'id': this.contact.id, + 'action': 'add-related-contact', + }); + } + + public onCommunicationsOpen(event: MouseEvent): void { + event.preventDefault(); + + this._router.navigate({ + entity: 'communication', + presentation: 'list', + contactId: this.contact.id + }, + { + matrixParams: { + contactFullName: `${this.contact.firstname} ${this.contact.lastname}` + } + } + ); + } + + public onCommunicationAdd(event: MouseEvent): void { + event.preventDefault(); + const popup: Popup = { + position: 'east', + anchor: event.target as Element, + }; + this._popupService.open(popup, { + entity: 'communication', + action: 'create', + contactId: this.contact.id + }); + } + + public onRelatedContactRemove(relatedContactId: string): void { + const relatedContactIds: string[] = this.form.controls[RELATED_PERSON_IDS].value; + this.form.controls[RELATED_PERSON_IDS].setValue(relatedContactIds.filter(it => it !== relatedContactId)); + } + + public ngOnDestroy(): void { + this._destroy$.next(); + } + + public contactTrackByFn: TrackByFunction = (index: number, contact: Contact): any => { + return contact.id; + }; +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact.model.ts b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact.model.ts new file mode 100644 index 000000000..3da0f124f --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact.model.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +export interface Contact { + id: string; + firstname: string; + lastname: string; + email: string; + phone: string; + street: string; + city: string; + relatedContactIds: string[]; +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact.module.ts b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact.module.ts new file mode 100644 index 000000000..7b45f4b75 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact.module.ts @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SciViewportModule } from '@scion/viewport'; +import { ContactRoutingModule } from './contact-routing.module'; +import { ReactiveFormsModule } from '@angular/forms'; +import { WorkbenchApplicationModule } from '@scion/workbench-application.angular'; +import { HttpClientModule } from '@angular/common/http'; +import { ContactActivityComponent } from './contact-activity/contact-activity.component'; +import { ContactViewComponent } from './contact-view/contact-view.component'; +import { ContactNewPopupComponent } from './contact-new-popup/contact-new-popup.component'; +import { RelatedContactAddPopupComponent } from './related-contact-add-popup/related-contact-add-popup.component'; +import { SciFilterFieldModule, SciListModule, SciPopupShellModule, SciSessionStorageModule } from '@scion/e2e/common'; +import { ContactService } from './contact.service'; + +@NgModule({ + declarations: [ + ContactActivityComponent, + ContactViewComponent, + ContactNewPopupComponent, + RelatedContactAddPopupComponent, + ], + imports: [ + CommonModule, + SciViewportModule, + ContactRoutingModule, + SciPopupShellModule, + SciSessionStorageModule, + SciFilterFieldModule, + SciListModule, + ReactiveFormsModule, + HttpClientModule, + WorkbenchApplicationModule.forChild(), + ], + exports: [], + providers: [ + ContactService + ] +}) +export class ContactModule { +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact.service.ts b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact.service.ts new file mode 100644 index 000000000..cd1f78b04 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/contact.service.ts @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Injectable, OnDestroy } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { filter, map, mergeMapTo, take, tap } from 'rxjs/operators'; +import { EMPTY, MonoTypeOperatorFunction, Observable, OperatorFunction, Subject } from 'rxjs'; +import { SciSessionStorageService, toFilterRegExp } from '@scion/e2e/common'; +import { Contact } from './contact.model'; +import { WorkbenchRouter } from '@scion/workbench-application.angular'; + +const PERSONS_STORAGE_KEY = 'contact.data'; + +@Injectable() +export class ContactService implements OnDestroy { + + private _destroy$ = new Subject(); + private _contacts$: Observable; + + constructor(httpClient: HttpClient, private _storage: SciSessionStorageService, private _router: WorkbenchRouter) { + this._contacts$ = this._storage.observe$(PERSONS_STORAGE_KEY, () => { + return httpClient.get('assets/contact.data.json').pipe(mapToContactDictionary()); + }); + } + + public contact$(id: string, options?: {once: boolean}): Observable { + return this._contacts$ + .pipe( + options && options.once ? take(1) : tap(), + map(dictionary => dictionary[id]), + filter(Boolean) + ); + } + + public contacts$(ids?: string[]): Observable { + return this._contacts$.pipe(mapToFilteredContactArray(ids)); + } + + public create$(contact: Contact): Observable { + return this._contacts$ + .pipe( + once(), + putContact(this._storage, contact), + mergeMapTo(EMPTY), + ); + } + + public update$(contact: Contact): Observable { + return this._contacts$ + .pipe( + once(), + putContact(this._storage, contact), + mergeMapTo(EMPTY), + ); + } + + public delete$(id: string): Observable { + return this._contacts$ + .pipe( + once(), + deleteContact(this._storage, id), + tap(() => this._router.navigate({entity: 'contact', id}, {closeIfPresent: true})), + mergeMapTo(EMPTY), + ); + } + + public ngOnDestroy(): void { + this._destroy$.next(); + } +} + +interface ContactDictionary { + [key: string]: any; +} + +function mapToContactDictionary(): OperatorFunction { + return map((contacts: Contact[]): ContactDictionary => { + return contacts.reduce((acc, contact) => ({...acc, ...{[contact.id]: contact}}), {}); + }); +} + +function mapToFilteredContactArray(ids: string[]): OperatorFunction { + return map((dictionary: ContactDictionary): Contact[] => { + return (ids || Object.keys(dictionary)) + .map(id => dictionary[id]) + .filter(Boolean) + .reduce((acc, contact) => [...acc, contact], [] as Contact[]) + .sort((p1, p2) => p1.firstname.localeCompare(p2.firstname)); + }); +} + +function putContact(storage: SciSessionStorageService, contact: Contact): MonoTypeOperatorFunction { + return tap((dictionary: ContactDictionary): void => { + dictionary[contact.id] = contact; + storage.put(PERSONS_STORAGE_KEY, dictionary); + }); +} + +function deleteContact(storage: SciSessionStorageService, id: string): MonoTypeOperatorFunction { + return tap((dictionary: ContactDictionary): void => { + delete dictionary[id]; + storage.put(PERSONS_STORAGE_KEY, dictionary); + }); +} + +function once(): MonoTypeOperatorFunction { + return take(1); +} + +export function filterContacts(): OperatorFunction<[string, Contact[]], Contact[]> { + return map(([filterText, contacts]: [string, Contact[]]): Contact[] => { + if (!filterText) { + return contacts; + } + + const filterRegExp = toFilterRegExp(filterText); + return contacts.filter(contact => filterRegExp.test(contact.firstname) || filterRegExp.test(contact.lastname) || filterRegExp.test(contact.city)); + }); +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/related-contact-add-popup/related-contact-add-popup.component.html b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/related-contact-add-popup/related-contact-add-popup.component.html new file mode 100644 index 000000000..dfda0e800 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/related-contact-add-popup/related-contact-add-popup.component.html @@ -0,0 +1,16 @@ + + Add related contact + + + + + {{contact.firstname}} {{contact.lastname}}
+ {{contact.city}} +
+
+
+ + OK +
diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/related-contact-add-popup/related-contact-add-popup.component.scss b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/related-contact-add-popup/related-contact-add-popup.component.scss new file mode 100644 index 000000000..b5bef06d6 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/related-contact-add-popup/related-contact-add-popup.component.scss @@ -0,0 +1,12 @@ +:host { + display: flex; + flex-direction: column; + + > sci-popup-shell { + flex: auto; + + sci-list { + flex: auto; + } + } +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/app/contact/related-contact-add-popup/related-contact-add-popup.component.ts b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/related-contact-add-popup/related-contact-add-popup.component.ts new file mode 100644 index 000000000..6fb5829f1 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/app/contact/related-contact-add-popup/related-contact-add-popup.component.ts @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, OnDestroy, TrackByFunction } from '@angular/core'; +import { provideWorkbenchPopup, WorkbenchPopup } from '@scion/workbench-application.angular'; +import { ContactService, filterContacts } from '../contact.service'; +import { BehaviorSubject, combineLatest, noop, Observable, Subject } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; +import { switchMap, takeUntil } from 'rxjs/operators'; +import { Contact } from '../contact.model'; + +@Component({ + selector: 'app-related-contact-add-popup', + templateUrl: './related-contact-add-popup.component.html', + styleUrls: ['./related-contact-add-popup.component.scss'], + providers: [ + provideWorkbenchPopup(RelatedContactAddPopupComponent) + ] +}) +export class RelatedContactAddPopupComponent implements OnDestroy { + + private _destroy$ = new Subject(); + private _relatedContactId: string; + private _filter$ = new BehaviorSubject(null); + + public contacts$: Observable; + + constructor(private _route: ActivatedRoute, + private _popup: WorkbenchPopup, + private _contactService: ContactService) { + this.contacts$ = combineLatest(this._filter$, this._contactService.contacts$()).pipe(filterContacts()); + } + + public onOption(relatedContactId: string): void { + this._relatedContactId = relatedContactId; + } + + public onOk(): void { + this._contactService.contact$(this._route.snapshot.params['id'], {once: true}) + .pipe( + switchMap(contact => this._contactService.update$({...contact, relatedContactIds: [...contact.relatedContactIds, this._relatedContactId]})), + takeUntil(this._destroy$), + ) + .subscribe(noop, noop, () => this._popup.close()); + } + + public onFilter(filterText: string): void { + this._filter$.next(filterText); + } + + public get valid(): boolean { + return !!this._relatedContactId; + } + + public ngOnDestroy(): void { + this._destroy$.next(); + } + + public trackByFn: TrackByFunction = (index: number, contact: Contact): any => { + return contact.id; + }; +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/assets/.gitkeep b/projects/e2e/workbench-application-platform/contact-app/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/projects/e2e/workbench-application-platform/contact-app/src/assets/contact.data.json b/projects/e2e/workbench-application-platform/contact-app/src/assets/contact.data.json new file mode 100644 index 000000000..586c4df12 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/assets/contact.data.json @@ -0,0 +1,2740 @@ +[ + { + "id": "1", + "firstname": "Marley", + "lastname": "Chidzoy", + "email": "mchidzoy0@oracle.com", + "phone": "424-561-5508", + "street": "08 Monterey Court", + "city": "New York", + "relatedContactIds": [ + "17", + "1", + "13", + "5", + "91", + "57", + "44", + "32", + "4", + "16", + "86", + "43", + "40", + "67", + "13", + "65", + "22", + "86", + "11", + "27", + "94", + "82", + "91", + "84", + "80", + "61", + "80", + "11", + "5" + ] + }, + { + "id": "2", + "firstname": "Arlen", + "lastname": "Bossel", + "email": "abossel1@bandcamp.com", + "phone": "205-853-0895", + "street": "1 Moulton Terrace", + "city": "Los Angeles", + "relatedContactIds": [ + "48", + "26", + "43", + "92", + "70", + "25", + "78", + "30" + ] + }, + { + "id": "3", + "firstname": "Sigismond", + "lastname": "Mulqueeny", + "email": "smulqueeny2@oaic.gov.au", + "phone": "737-861-1860", + "street": "15 Hintze Point", + "city": "Chicago", + "relatedContactIds": [ + "33", + "97", + "82", + "88", + "76", + "99", + "16", + "39", + "18", + "75", + "38", + "11", + "52", + "79" + ] + }, + { + "id": "4", + "firstname": "Brooke", + "lastname": "Hulmes", + "email": "bhulmes3@1und1.de", + "phone": "667-900-2300", + "street": "5 Moland Lane", + "city": "Houston", + "relatedContactIds": [ + "82", + "7", + "95", + "63", + "16", + "47", + "14", + "37", + "14", + "72", + "53", + "11" + ] + }, + { + "id": "5", + "firstname": "Robbie", + "lastname": "Bannon", + "email": "rbannon4@ebay.co.uk", + "phone": "127-927-7189", + "street": "685 Hansons Pass", + "city": "Philadelphia", + "relatedContactIds": [ + "30", + "96", + "93", + "6", + "100", + "85", + "100", + "95", + "11", + "75", + "88", + "35", + "26", + "49", + "96", + "36", + "21", + "87", + "18", + "53" + ] + }, + { + "id": "6", + "firstname": "Chastity", + "lastname": "Schumacher", + "email": "cschumacher5@youtube.com", + "phone": "627-896-4939", + "street": "2 Badeau Center", + "city": "Phoenix", + "relatedContactIds": [ + "45", + "99", + "90", + "35", + "58", + "49", + "35", + "45", + "14", + "88", + "33", + "89", + "54", + "11", + "35", + "98", + "40", + "22", + "51", + "52", + "33" + ] + }, + { + "id": "7", + "firstname": "Ric", + "lastname": "Spelsbury", + "email": "rspelsbury6@over-blog.com", + "phone": "120-113-6252", + "street": "52558 Del Sol Road", + "city": "San Antonio", + "relatedContactIds": [ + "27", + "67", + "13", + "31", + "60", + "10", + "82", + "56", + "72", + "1", + "27", + "53", + "88", + "8", + "52", + "36", + "72", + "15", + "56", + "58", + "26", + "13", + "65", + "40", + "35", + "75", + "2", + "18" + ] + }, + { + "id": "8", + "firstname": "Sherilyn", + "lastname": "Baselio", + "email": "sbaselio7@dion.ne.jp", + "phone": "336-625-1423", + "street": "19 Carey Point", + "city": "San Diego", + "relatedContactIds": [ + "79", + "60", + "89", + "43", + "4", + "37", + "82", + "39", + "54", + "58", + "45", + "9", + "20", + "51", + "42", + "3", + "57", + "93", + "97", + "4", + "81" + ] + }, + { + "id": "9", + "firstname": "Allianora", + "lastname": "Boldt", + "email": "aboldt8@cpanel.net", + "phone": "308-983-4275", + "street": "41 Blaine Road", + "city": "Dallas", + "relatedContactIds": [ + "4", + "66", + "84", + "1", + "83", + "84", + "70", + "45", + "21", + "33", + "69", + "49", + "72", + "19", + "30", + "47", + "6", + "86", + "60", + "67", + "7", + "82", + "6", + "81", + "99", + "11" + ] + }, + { + "id": "10", + "firstname": "Priscilla", + "lastname": "Duffitt", + "email": "pduffitt9@economist.com", + "phone": "919-470-6524", + "street": "967 Blaine Center", + "city": "10", + "relatedContactIds": [ + "8", + "50", + "57", + "11", + "25", + "43", + "39", + "13", + "5", + "20", + "19", + "57" + ] + }, + { + "id": "11", + "firstname": "Darcy", + "lastname": "Hammelberg", + "email": "dhammelberga@thetimes.co.uk", + "phone": "944-291-7076", + "street": "21621 Merry Trail", + "city": "New York", + "relatedContactIds": [ + "1", + "53", + "53", + "3", + "88", + "56", + "49", + "47", + "38", + "39", + "64", + "51", + "15", + "27", + "35", + "97" + ] + }, + { + "id": "12", + "firstname": "Kennie", + "lastname": "Klessmann", + "email": "kklessmannb@pagesperso-orange.fr", + "phone": "199-728-6721", + "street": "17 Garrison Circle", + "city": "New York", + "relatedContactIds": [ + "35" + ] + }, + { + "id": "13", + "firstname": "Zorana", + "lastname": "Jankovic", + "email": "zjankovicc@paypal.com", + "phone": "760-147-5671", + "street": "311 Del Mar Park", + "city": "New York", + "relatedContactIds": [ + "57", + "73", + "79", + "5", + "45", + "22", + "99", + "39", + "19", + "23", + "68", + "47", + "8", + "51", + "12", + "49", + "19" + ] + }, + { + "id": "14", + "firstname": "Cheryl", + "lastname": "Mattea", + "email": "cmattead@webs.com", + "phone": "356-935-1878", + "street": "7774 Crescent Oaks Road", + "city": "New York", + "relatedContactIds": [ + "3", + "12", + "24", + "96", + "64", + "2", + "94", + "35", + "93", + "93", + "46", + "74", + "82", + "48", + "18", + "6", + "28", + "17", + "16" + ] + }, + { + "id": "15", + "firstname": "Clarke", + "lastname": "Noden", + "email": "cnodene@google.ru", + "phone": "839-291-9187", + "street": "37 Anhalt Street", + "city": "New York", + "relatedContactIds": [ + "6", + "97", + "66", + "61", + "36", + "74", + "38", + "76", + "86", + "25", + "37", + "42", + "90", + "91", + "27" + ] + }, + { + "id": "16", + "firstname": "Giralda", + "lastname": "Fackrell", + "email": "gfackrellf@ucsd.edu", + "phone": "996-969-6842", + "street": "56509 Tennyson Park", + "city": "New York", + "relatedContactIds": [ + "41", + "86", + "75", + "49", + "92", + "93", + "47", + "96", + "26", + "57", + "76", + "75", + "55", + "5", + "88", + "21", + "25", + "4", + "87", + "11", + "63", + "16", + "51", + "70", + "65" + ] + }, + { + "id": "17", + "firstname": "Sheppard", + "lastname": "Bolstridge", + "email": "sbolstridgeg@wsj.com", + "phone": "982-513-7145", + "street": "00358 Monterey Terrace", + "city": "New York", + "relatedContactIds": [ + "15", + "70", + "35", + "17", + "3", + "61", + "88", + "86", + "93", + "46", + "3" + ] + }, + { + "id": "18", + "firstname": "Garvey", + "lastname": "McKinnon", + "email": "gmckinnonh@hatena.ne.jp", + "phone": "281-541-9252", + "street": "50809 High Crossing Alley", + "city": "Fort Worth", + "relatedContactIds": [ + "21", + "49", + "20", + "44", + "47", + "16", + "73", + "5", + "84", + "57", + "61", + "63", + "67", + "99", + "45", + "9", + "24", + "39", + "18", + "66" + ] + }, + { + "id": "19", + "firstname": "Elden", + "lastname": "Ring", + "email": "eringi@g.co", + "phone": "265-527-5671", + "street": "62470 Gina Plaza", + "city": "Fort Worth", + "relatedContactIds": [ + "7", + "3", + "62", + "24", + "60", + "10", + "6", + "21" + ] + }, + { + "id": "20", + "firstname": "Connie", + "lastname": "Ceschi", + "email": "cceschij@cafepress.com", + "phone": "687-266-4082", + "street": "70972 Stuart Avenue", + "city": "Fort Worth", + "relatedContactIds": [ + "73", + "23", + "74", + "55", + "65", + "95", + "45", + "4", + "21", + "99", + "56", + "92", + "83", + "26", + "66", + "86", + "26", + "67", + "90", + "68", + "70", + "38", + "100", + "79", + "64", + "37", + "46", + "94" + ] + }, + { + "id": "21", + "firstname": "Charmion", + "lastname": "Phillipp", + "email": "cphillippk@elpais.com", + "phone": "749-465-6113", + "street": "489 Lindbergh Parkway", + "city": "Fort Worth", + "relatedContactIds": [ + "71", + "92", + "74", + "72", + "37", + "24", + "55", + "42", + "29", + "92", + "64", + "29", + "32", + "100", + "52", + "33", + "52", + "33", + "68", + "93", + "77", + "77", + "23", + "69", + "75", + "59", + "42" + ] + }, + { + "id": "22", + "firstname": "Dane", + "lastname": "Palay", + "email": "dpalayl@howstuffworks.com", + "phone": "695-766-3982", + "street": "5444 Clyde Gallagher Terrace", + "city": "Fort Worth", + "relatedContactIds": [ + "2", + "62", + "10", + "76", + "11", + "89", + "74", + "32", + "4", + "27", + "27", + "50", + "79", + "89", + "93", + "57", + "91", + "89", + "13", + "41", + "49", + "13", + "67", + "96", + "77" + ] + }, + { + "id": "23", + "firstname": "Devy", + "lastname": "Bulleyn", + "email": "dbulleynm@ehow.com", + "phone": "996-426-0038", + "street": "7330 Novick Parkway", + "city": "Fort Worth", + "relatedContactIds": [ + "33", + "60", + "93", + "47", + "3", + "39", + "20", + "11", + "34", + "19", + "90", + "52", + "87", + "86", + "74", + "84", + "29", + "56", + "73", + "65", + "23", + "78", + "33", + "88", + "92", + "30", + "76", + "92", + "89", + "57" + ] + }, + { + "id": "24", + "firstname": "Marlo", + "lastname": "Angus", + "email": "mangusn@ted.com", + "phone": "693-385-3039", + "street": "2458 Mandrake Park", + "city": "Detroit", + "relatedContactIds": [ + "99", + "7", + "68", + "78", + "41", + "91", + "7", + "36", + "31", + "25", + "65", + "77", + "19", + "1", + "62", + "96", + "95", + "47", + "87", + "41", + "17", + "42", + "38" + ] + }, + { + "id": "25", + "firstname": "Cassandra", + "lastname": "Ingerson", + "email": "cingersono@hc360.com", + "phone": "172-961-9547", + "street": "1832 Northport Place", + "city": "Detroit", + "relatedContactIds": [ + "23", + "7", + "100", + "77", + "21", + "7", + "5", + "39", + "64", + "32", + "23", + "84", + "54", + "66", + "59", + "26", + "7", + "77", + "22", + "14", + "88", + "83", + "4", + "18", + "52", + "68" + ] + }, + { + "id": "26", + "firstname": "Felix", + "lastname": "Lilley", + "email": "flilleyp@cocolog-nifty.com", + "phone": "163-958-5543", + "street": "93585 Grasskamp Junction", + "city": "Detroit", + "relatedContactIds": [ + "44", + "71", + "53", + "67", + "100", + "100" + ] + }, + { + "id": "27", + "firstname": "Brigham", + "lastname": "Fidell", + "email": "bfidellq@quantcast.com", + "phone": "916-782-2894", + "street": "55067 Emmet Terrace", + "city": "Detroit", + "relatedContactIds": [ + "22", + "31" + ] + }, + { + "id": "28", + "firstname": "Darrell", + "lastname": "Yo", + "email": "dyor@psu.edu", + "phone": "829-854-6749", + "street": "3370 Moland Alley", + "city": "El Paso", + "relatedContactIds": [ + "45", + "71", + "4", + "83", + "25", + "52", + "62", + "29", + "74", + "30", + "73", + "1", + "87", + "18", + "38", + "47", + "14", + "43", + "56", + "64", + "25", + "3", + "54", + "72", + "33", + "5", + "9" + ] + }, + { + "id": "29", + "firstname": "Forster", + "lastname": "O'Gaven", + "email": "fogavens@auda.org.au", + "phone": "410-837-5493", + "street": "0 Sunnyside Junction", + "city": "El Paso", + "relatedContactIds": [ + "21", + "16", + "10", + "79", + "71", + "34", + "89", + "10", + "80", + "44", + "96", + "93", + "93", + "2", + "91", + "14", + "85", + "39", + "25", + "91" + ] + }, + { + "id": "30", + "firstname": "Fayina", + "lastname": "Durtnall", + "email": "fdurtnallt@creativecommons.org", + "phone": "613-332-4522", + "street": "49 Cascade Circle", + "city": "El Paso", + "relatedContactIds": [ + "66", + "14", + "87", + "20", + "1", + "41", + "91", + "23", + "30", + "19", + "29", + "56", + "43", + "63", + "55", + "83" + ] + }, + { + "id": "31", + "firstname": "Woodie", + "lastname": "Thompson", + "email": "wthompsonu@wp.com", + "phone": "218-925-3687", + "street": "3 Monument Parkway", + "city": "El Paso", + "relatedContactIds": [ + "89", + "79", + "22", + "59", + "6", + "49" + ] + }, + { + "id": "32", + "firstname": "Ozzy", + "lastname": "Emanuele", + "email": "oemanuelev@google.co.jp", + "phone": "580-405-2775", + "street": "0 American Ash Way", + "city": "El Paso", + "relatedContactIds": [ + "21", + "42", + "100", + "2", + "21", + "35", + "10", + "63", + "60", + "12", + "74", + "12", + "36", + "81", + "96", + "31", + "74", + "30" + ] + }, + { + "id": "33", + "firstname": "Reggis", + "lastname": "Silverston", + "email": "rsilverstonw@blogspot.com", + "phone": "181-855-1848", + "street": "16369 Rieder Road", + "city": "Memphis", + "relatedContactIds": [ + "31", + "29" + ] + }, + { + "id": "34", + "firstname": "Shane", + "lastname": "Hounsom", + "email": "shounsomx@sbwire.com", + "phone": "639-282-0481", + "street": "2564 Melrose Way", + "city": "Memphis", + "relatedContactIds": [ + "79", + "85", + "50", + "75", + "87", + "11", + "89", + "37", + "5", + "78", + "11", + "41", + "54", + "92", + "46", + "95", + "93", + "63", + "45", + "35", + "66", + "16", + "62", + "100", + "86", + "98" + ] + }, + { + "id": "35", + "firstname": "Audi", + "lastname": "Marriott", + "email": "amarriotty@vistaprint.com", + "phone": "394-428-1689", + "street": "189 Alpine Point", + "city": "Memphis", + "relatedContactIds": [ + "1", + "4", + "99", + "19", + "45", + "18", + "14", + "24", + "73", + "68", + "41", + "44", + "3", + "83", + "16", + "94", + "24", + "36", + "51", + "84", + "45", + "54", + "33", + "43", + "47", + "73", + "10", + "99" + ] + }, + { + "id": "36", + "firstname": "Starlin", + "lastname": "Stidston", + "email": "sstidstonz@scientificamerican.com", + "phone": "906-227-3210", + "street": "827 Blaine Point", + "city": "Memphis", + "relatedContactIds": [ + "23", + "45", + "79", + "93", + "23" + ] + }, + { + "id": "37", + "firstname": "Nathan", + "lastname": "Scrogges", + "email": "nscrogges10@gnu.org", + "phone": "154-685-9931", + "street": "886 Service Circle", + "city": "Memphis", + "relatedContactIds": [ + "62", + "38", + "94", + "97" + ] + }, + { + "id": "38", + "firstname": "Adeline", + "lastname": "McEnhill", + "email": "amcenhill11@baidu.com", + "phone": "727-842-2730", + "street": "0 Annamark Place", + "city": "Seattle", + "relatedContactIds": [ + "63", + "95", + "11", + "46", + "61", + "44", + "59", + "1", + "92", + "86", + "11", + "47", + "78", + "9", + "64", + "24", + "76", + "54", + "44", + "71", + "73", + "42", + "1", + "4", + "10", + "54", + "90", + "37", + "64" + ] + }, + { + "id": "39", + "firstname": "Alaster", + "lastname": "Sancto", + "email": "asancto12@prnewswire.com", + "phone": "409-222-0492", + "street": "8 Doe Crossing Center", + "city": "Seattle", + "relatedContactIds": [ + "98", + "11", + "78", + "33", + "57", + "85", + "81", + "38", + "10", + "41", + "77", + "17", + "74", + "52", + "86", + "27", + "86", + "62", + "100" + ] + }, + { + "id": "40", + "firstname": "Garret", + "lastname": "Turnock", + "email": "gturnock13@cbslocal.com", + "phone": "287-728-4389", + "street": "947 Utah Junction", + "city": "Seattle", + "relatedContactIds": [ + "13", + "98", + "30", + "16", + "99", + "43", + "53", + "40" + ] + }, + { + "id": "41", + "firstname": "Nappy", + "lastname": "Lenaghen", + "email": "nlenaghen14@delicious.com", + "phone": "786-344-7171", + "street": "54 Marquette Avenue", + "city": "Seattle", + "relatedContactIds": [ + "34", + "69", + "25", + "42", + "19", + "48", + "48", + "28", + "52", + "32", + "69", + "78", + "74", + "86", + "43", + "35", + "62", + "32", + "43", + "16" + ] + }, + { + "id": "42", + "firstname": "Eberhard", + "lastname": "Dawltrey", + "email": "edawltrey15@weather.com", + "phone": "374-975-6888", + "street": "1 Anhalt Trail", + "city": "Seattle", + "relatedContactIds": [ + "44", + "85", + "30" + ] + }, + { + "id": "43", + "firstname": "Sarette", + "lastname": "Bateson", + "email": "sbateson16@163.com", + "phone": "497-657-1837", + "street": "986 Dennis Way", + "city": "Seattle", + "relatedContactIds": [ + "3", + "92", + "100", + "78", + "9", + "22", + "62", + "5", + "69", + "2", + "52", + "49", + "91", + "7", + "60", + "41", + "26", + "41" + ] + }, + { + "id": "44", + "firstname": "Katha", + "lastname": "Seamon", + "email": "kseamon17@nationalgeographic.com", + "phone": "913-934-5362", + "street": "64 Burrows Plaza", + "city": "Denver", + "relatedContactIds": [ + "61", + "11", + "2", + "19", + "18", + "61", + "63", + "82" + ] + }, + { + "id": "45", + "firstname": "Nanny", + "lastname": "Schwand", + "email": "nschwand18@blog.com", + "phone": "286-800-6111", + "street": "978 Butternut Alley", + "city": "Denver", + "relatedContactIds": [ + "24", + "24", + "63", + "75", + "7", + "78", + "98", + "39", + "29", + "94" + ] + }, + { + "id": "46", + "firstname": "Prent", + "lastname": "Remer", + "email": "premer19@pagesperso-orange.fr", + "phone": "907-185-4077", + "street": "77 Scott Way", + "city": "Denver", + "relatedContactIds": [ + "94", + "55" + ] + }, + { + "id": "47", + "firstname": "Brendon", + "lastname": "Bickerton", + "email": "bbickerton1a@nhs.uk", + "phone": "709-973-0138", + "street": "0494 Scoville Street", + "city": "Denver", + "relatedContactIds": [ + "17", + "92", + "6", + "27", + "21", + "24", + "51", + "83", + "43", + "81", + "61", + "93", + "60", + "61", + "75", + "68", + "61", + "93" + ] + }, + { + "id": "48", + "firstname": "Cyndia", + "lastname": "Christofides", + "email": "cchristofides1b@slashdot.org", + "phone": "920-603-4653", + "street": "0 Briar Crest Circle", + "city": "Denver", + "relatedContactIds": [ + "47", + "20", + "58", + "56", + "58", + "92", + "48", + "92", + "92", + "94", + "29", + "70", + "39", + "62", + "64", + "35", + "77", + "55" + ] + }, + { + "id": "49", + "firstname": "Lyon", + "lastname": "Vannoort", + "email": "lvannoort1c@istockphoto.com", + "phone": "821-630-4226", + "street": "57 Colorado Parkway", + "city": "Denver", + "relatedContactIds": [ + "88", + "52", + "53", + "83", + "92", + "62", + "60", + "56", + "54", + "22" + ] + }, + { + "id": "50", + "firstname": "Riane", + "lastname": "Peniman", + "email": "rpeniman1d@google.ru", + "phone": "473-151-6582", + "street": "6 Harbort Road", + "city": "Washington", + "relatedContactIds": [ + "26", + "57", + "1" + ] + }, + { + "id": "51", + "firstname": "Sidoney", + "lastname": "Ainscow", + "email": "sainscow1e@mayoclinic.com", + "phone": "981-848-5913", + "street": "47 Vernon Hill", + "city": "Washington", + "relatedContactIds": [ + "79", + "68", + "54", + "62", + "24", + "36", + "28", + "92", + "84", + "41" + ] + }, + { + "id": "52", + "firstname": "Jaime", + "lastname": "Rastall", + "email": "jrastall1f@infoseek.co.jp", + "phone": "129-755-7965", + "street": "8 Di Loreto Plaza", + "city": "Washington", + "relatedContactIds": [ + "10", + "28", + "71", + "10", + "87", + "79", + "66", + "3", + "71", + "27", + "19", + "49", + "18", + "40", + "52", + "69", + "70", + "82", + "90", + "55", + "29" + ] + }, + { + "id": "53", + "firstname": "Cliff", + "lastname": "Bowmer", + "email": "cbowmer1g@google.it", + "phone": "670-665-7680", + "street": "8820 Charing Cross Drive", + "city": "Washington", + "relatedContactIds": [ + "66", + "58", + "63", + "7", + "46", + "64", + "36", + "56", + "20", + "28", + "78", + "94", + "2", + "28", + "96" + ] + }, + { + "id": "54", + "firstname": "Alli", + "lastname": "Astlet", + "email": "aastlet1h@google.com.br", + "phone": "690-185-6023", + "street": "37 Kropf Point", + "city": "Washington", + "relatedContactIds": [ + "88", + "43", + "80", + "30", + "35", + "30", + "79", + "10", + "79" + ] + }, + { + "id": "55", + "firstname": "Stacia", + "lastname": "Aspinell", + "email": "saspinell1i@netscape.com", + "phone": "690-252-1053", + "street": "37983 Vernon Place", + "city": "Boston", + "relatedContactIds": [ + "78", + "19", + "36", + "51", + "21", + "52", + "47", + "28", + "66", + "80", + "26", + "17", + "27", + "59", + "90", + "60", + "94", + "33", + "23" + ] + }, + { + "id": "56", + "firstname": "Cristal", + "lastname": "Heining", + "email": "cheining1j@apache.org", + "phone": "652-391-3748", + "street": "8979 Arapahoe Plaza", + "city": "Boston", + "relatedContactIds": [ + "62", + "41", + "24", + "69", + "40", + "52" + ] + }, + { + "id": "57", + "firstname": "Urbain", + "lastname": "Robelet", + "email": "urobelet1k@amazon.com", + "phone": "302-990-8314", + "street": "2 Lotheville Junction", + "city": "Boston", + "relatedContactIds": [ + "87", + "7", + "2", + "99", + "39", + "76", + "92", + "48", + "9", + "59", + "42", + "6", + "58", + "41", + "77", + "96", + "25", + "33", + "44", + "24", + "19", + "73", + "32", + "49", + "47", + "66", + "67", + "8" + ] + }, + { + "id": "58", + "firstname": "Grete", + "lastname": "Powton", + "email": "gpowton1l@360.cn", + "phone": "989-259-5750", + "street": "5784 Sugar Court", + "city": "Boston", + "relatedContactIds": [ + "1", + "13", + "21", + "9", + "88", + "38", + "9", + "80", + "74", + "46", + "88", + "16", + "34", + "2", + "27", + "18", + "1", + "69", + "40", + "66", + "70", + "51", + "60", + "98", + "56", + "21", + "15", + "66", + "3", + "96" + ] + }, + { + "id": "59", + "firstname": "Bernadine", + "lastname": "Antonoczyk", + "email": "bantonoczyk1m@csmonitor.com", + "phone": "635-323-8492", + "street": "8056 Basil Crossing", + "city": "Boston", + "relatedContactIds": [ + "55", + "28", + "77", + "86", + "24" + ] + }, + { + "id": "60", + "firstname": "Carlyn", + "lastname": "Leroux", + "email": "cleroux1n@discovery.com", + "phone": "155-727-0433", + "street": "39 Cordelia Center", + "city": "Boston", + "relatedContactIds": [ + "52", + "83", + "9", + "89", + "6", + "86", + "35", + "77", + "79", + "15", + "60", + "1", + "28", + "66", + "62", + "96", + "95", + "76", + "48", + "2" + ] + }, + { + "id": "61", + "firstname": "Larina", + "lastname": "Southouse", + "email": "lsouthouse1o@symantec.com", + "phone": "144-170-9302", + "street": "58735 Fuller Park", + "city": "Boston", + "relatedContactIds": [ + "16", + "51", + "46", + "95", + "37", + "56", + "50", + "30", + "69", + "77", + "77", + "87", + "82", + "24", + "78", + "88", + "14", + "99", + "86" + ] + }, + { + "id": "62", + "firstname": "Yvon", + "lastname": "Zorer", + "email": "yzorer1p@sogou.com", + "phone": "339-521-0522", + "street": "8 Hintze Road", + "city": "Boston", + "relatedContactIds": [ + "30", + "49", + "3", + "52", + "76", + "88", + "6", + "4", + "43", + "71", + "5", + "29", + "79", + "88", + "84", + "70", + "98", + "65", + "40", + "45", + "81", + "66", + "60", + "81", + "79", + "90", + "1" + ] + }, + { + "id": "63", + "firstname": "Salome", + "lastname": "Edscer", + "email": "sedscer1q@networkadvertising.org", + "phone": "189-468-3364", + "street": "435 Buell Lane", + "city": "Boston", + "relatedContactIds": [ + "47", + "2", + "60", + "88", + "54", + "97", + "15", + "38", + "28", + "94", + "64", + "58", + "53", + "12", + "77", + "37", + "3", + "74", + "28", + "5" + ] + }, + { + "id": "64", + "firstname": "Raynor", + "lastname": "Souter", + "email": "rsouter1r@linkedin.com", + "phone": "606-446-3047", + "street": "1372 Forest Court", + "city": "Boston", + "relatedContactIds": [ + "82", + "32", + "80", + "14", + "6", + "60", + "38", + "85", + "20", + "94", + "4", + "2", + "44", + "89", + "33", + "57", + "85" + ] + }, + { + "id": "65", + "firstname": "Orson", + "lastname": "Gross", + "email": "ogross1s@dmoz.org", + "phone": "218-852-0070", + "street": "54 Burning Wood Circle", + "city": "Boston", + "relatedContactIds": [ + "62", + "57", + "40", + "38", + "93", + "78", + "4", + "78" + ] + }, + { + "id": "66", + "firstname": "Biddie", + "lastname": "Bestwall", + "email": "bbestwall1t@blogspot.com", + "phone": "355-359-3510", + "street": "2681 Hanover Park", + "city": "Nashville", + "relatedContactIds": [ + "20", + "39", + "6", + "8", + "36", + "14", + "51", + "75", + "16", + "23", + "32" + ] + }, + { + "id": "67", + "firstname": "Yul", + "lastname": "Hakonsen", + "email": "yhakonsen1u@booking.com", + "phone": "824-179-8742", + "street": "74332 Schurz Circle", + "city": "Nashville", + "relatedContactIds": [ + "86", + "79", + "38", + "1", + "71", + "61" + ] + }, + { + "id": "68", + "firstname": "Dorice", + "lastname": "Kynvin", + "email": "dkynvin1v@microsoft.com", + "phone": "299-153-8252", + "street": "02823 Brickson Park Pass", + "city": "Nashville", + "relatedContactIds": [ + "98", + "77", + "18", + "39", + "3", + "92", + "19", + "92", + "96", + "68", + "28", + "68", + "70", + "57", + "20", + "42", + "59", + "4", + "37" + ] + }, + { + "id": "69", + "firstname": "Angus", + "lastname": "Berge", + "email": "aberge1w@amazon.de", + "phone": "882-892-3642", + "street": "4 Scoville Terrace", + "city": "Nashville", + "relatedContactIds": [ + "11", + "75", + "12", + "32", + "47", + "92", + "90", + "50", + "86", + "55", + "64", + "69", + "97", + "84", + "66", + "64", + "18", + "2", + "39", + "4", + "97" + ] + }, + { + "id": "70", + "firstname": "Noellyn", + "lastname": "Mercer", + "email": "nmercer1x@bizjournals.com", + "phone": "587-521-2523", + "street": "4856 Declaration Terrace", + "city": "Nashville", + "relatedContactIds": [ + "65", + "18", + "58", + "92", + "47", + "64", + "4", + "88", + "48", + "47", + "31", + "73", + "70", + "63", + "9", + "54", + "6", + "46", + "91", + "61", + "89" + ] + }, + { + "id": "71", + "firstname": "Jarred", + "lastname": "Loader", + "email": "jloader1y@skype.com", + "phone": "167-538-7946", + "street": "12 Warbler Way", + "city": "Nashville", + "relatedContactIds": [ + "4", + "72", + "60", + "68", + "77", + "2", + "35", + "4", + "99", + "98", + "31", + "16", + "72", + "10", + "88", + "4", + "74", + "74", + "65", + "70", + "78", + "21", + "93", + "54" + ] + }, + { + "id": "72", + "firstname": "Etan", + "lastname": "Abate", + "email": "eabate1z@google.pl", + "phone": "183-777-0106", + "street": "14 Vernon Alley", + "city": "Nashville", + "relatedContactIds": [ + "10", + "88", + "95", + "72", + "39", + "56", + "5", + "24", + "49", + "85", + "17", + "6", + "4", + "27", + "24", + "13", + "5", + "71" + ] + }, + { + "id": "73", + "firstname": "Ella", + "lastname": "Burnham", + "email": "eburnham20@google.ca", + "phone": "266-930-2209", + "street": "8588 Maywood Road", + "city": "Nashville", + "relatedContactIds": [ + "99", + "55", + "45", + "25", + "48", + "21", + "51", + "94", + "39", + "72", + "56", + "50" + ] + }, + { + "id": "74", + "firstname": "Enrique", + "lastname": "Moyce", + "email": "emoyce21@furl.net", + "phone": "338-751-0068", + "street": "5883 Corscot Pass", + "city": "Baltimore", + "relatedContactIds": [ + "61", + "18", + "100", + "43", + "29", + "9", + "80", + "41", + "11", + "54", + "75", + "27", + "34", + "79", + "30", + "56", + "66", + "59", + "70", + "11", + "96", + "55", + "67", + "60", + "48", + "86", + "75", + "56", + "90", + "10" + ] + }, + { + "id": "75", + "firstname": "Alvin", + "lastname": "Zambon", + "email": "azambon22@mozilla.com", + "phone": "551-439-1644", + "street": "37947 Gateway Way", + "city": "Baltimore", + "relatedContactIds": [ + "91", + "87", + "3", + "68", + "87", + "78", + "58", + "66", + "30", + "35", + "87", + "39", + "8", + "81", + "60", + "87", + "56", + "91", + "31", + "89", + "75", + "28", + "71", + "43" + ] + }, + { + "id": "76", + "firstname": "Kristian", + "lastname": "Coxhead", + "email": "kcoxhead23@bing.com", + "phone": "479-956-9226", + "street": "2 Tennyson Circle", + "city": "Baltimore", + "relatedContactIds": [ + "8", + "95", + "86", + "75", + "8", + "49", + "64", + "71", + "31", + "90", + "19", + "11", + "54", + "54", + "30", + "47", + "6", + "24", + "19", + "75" + ] + }, + { + "id": "77", + "firstname": "Pancho", + "lastname": "Woloschinski", + "email": "pwoloschinski24@hhs.gov", + "phone": "842-799-5251", + "street": "78 Calypso Pass", + "city": "Baltimore", + "relatedContactIds": [ + "81", + "23", + "10", + "76", + "38", + "26", + "56", + "45", + "40", + "20", + "92", + "3", + "83", + "39", + "19", + "42", + "44", + "53", + "10", + "77", + "68", + "45", + "3", + "33" + ] + }, + { + "id": "78", + "firstname": "Berti", + "lastname": "Wolfers", + "email": "bwolfers25@about.me", + "phone": "158-232-4876", + "street": "6105 Loomis Road", + "city": "Baltimore", + "relatedContactIds": [ + "8", + "81", + "76", + "63", + "68", + "30", + "1", + "40", + "95", + "47", + "49", + "12", + "7" + ] + }, + { + "id": "79", + "firstname": "Shelbi", + "lastname": "Gartan", + "email": "sgartan26@canalblog.com", + "phone": "270-316-1250", + "street": "384 Tomscot Hill", + "city": "Baltimore", + "relatedContactIds": [ + "81", + "18", + "90", + "13", + "75", + "75", + "99", + "4", + "18", + "72", + "84", + "8", + "55" + ] + }, + { + "id": "80", + "firstname": "Cristi", + "lastname": "Kull", + "email": "ckull27@elpais.com", + "phone": "158-767-7757", + "street": "93468 Spaight Trail", + "city": "Baltimore", + "relatedContactIds": [ + "30", + "26", + "31", + "84", + "100", + "21", + "38", + "58", + "69", + "42", + "53", + "64", + "33", + "71", + "54", + "82", + "92", + "70", + "49", + "43", + "14", + "53", + "31", + "54", + "46", + "73", + "82", + "78" + ] + }, + { + "id": "81", + "firstname": "Laurent", + "lastname": "Le Count", + "email": "llecount28@lycos.com", + "phone": "366-236-8102", + "street": "1 North Terrace", + "city": "Baltimore", + "relatedContactIds": [ + "45", + "7" + ] + }, + { + "id": "82", + "firstname": "Lodovico", + "lastname": "Benezet", + "email": "lbenezet29@dailymotion.com", + "phone": "258-407-0659", + "street": "06769 Dixon Center", + "city": "Baltimore", + "relatedContactIds": [ + "27", + "4", + "3", + "18", + "15", + "90", + "57", + "57", + "49", + "83", + "16", + "44", + "97", + "42", + "56", + "4", + "40" + ] + }, + { + "id": "83", + "firstname": "Beverlie", + "lastname": "Hollyman", + "email": "bhollyman2a@webs.com", + "phone": "684-917-1551", + "street": "6709 Westend Road", + "city": "Baltimore", + "relatedContactIds": [ + "43", + "92", + "44" + ] + }, + { + "id": "84", + "firstname": "Bronny", + "lastname": "Shalloo", + "email": "bshalloo2b@newyorker.com", + "phone": "897-305-8712", + "street": "234 Grayhawk Trail", + "city": "Oklahoma City", + "relatedContactIds": [ + "24", + "84", + "45", + "14", + "32", + "39", + "19", + "53", + "98", + "34", + "73", + "95", + "15", + "23", + "52", + "88", + "87", + "52", + "76" + ] + }, + { + "id": "85", + "firstname": "Ashien", + "lastname": "Wittleton", + "email": "awittleton2c@statcounter.com", + "phone": "593-414-1162", + "street": "471 Twin Pines Point", + "city": "Oklahoma City", + "relatedContactIds": [ + "18", + "88", + "42" + ] + }, + { + "id": "86", + "firstname": "Datha", + "lastname": "Stuttman", + "email": "dstuttman2d@apache.org", + "phone": "274-213-4881", + "street": "5667 Bonner Drive", + "city": "Oklahoma City", + "relatedContactIds": [ + "36", + "9" + ] + }, + { + "id": "87", + "firstname": "Averell", + "lastname": "Whates", + "email": "awhates2e@pcworld.com", + "phone": "901-509-3869", + "street": "21 Dottie Crossing", + "city": "Oklahoma City", + "relatedContactIds": [ + "42", + "29", + "43", + "17", + "67", + "59", + "10", + "63", + "10", + "16", + "34", + "43", + "76", + "61", + "92", + "46", + "56", + "65", + "52", + "33", + "45", + "30", + "84", + "42", + "18", + "12", + "6", + "44", + "34" + ] + }, + { + "id": "88", + "firstname": "Katharine", + "lastname": "Kassel", + "email": "kkassel2f@google.ca", + "phone": "179-877-5021", + "street": "25 Moose Street", + "city": "Oklahoma City", + "relatedContactIds": [ + "66", + "64", + "49", + "26", + "23", + "22", + "24", + "58", + "96", + "98", + "39", + "11", + "22", + "87" + ] + }, + { + "id": "89", + "firstname": "Kingsley", + "lastname": "Pfaffel", + "email": "kpfaffel2g@amazonaws.com", + "phone": "423-747-9306", + "street": "5 Crest Line Circle", + "city": "Oklahoma City", + "relatedContactIds": [ + "34", + "23", + "87", + "22", + "16", + "51", + "69", + "5", + "54", + "56", + "12", + "92", + "51", + "77", + "33", + "90", + "32", + "6", + "42", + "7", + "25", + "8", + "77", + "75", + "88", + "86", + "98" + ] + }, + { + "id": "90", + "firstname": "Hobart", + "lastname": "Tosh", + "email": "htosh2h@tmall.com", + "phone": "760-935-3287", + "street": "95772 6th Junction", + "city": "Portland", + "relatedContactIds": [ + "52", + "41", + "87", + "25", + "39", + "10", + "12", + "13", + "65", + "92" + ] + }, + { + "id": "91", + "firstname": "Marlon", + "lastname": "Curm", + "email": "mcurm2i@nytimes.com", + "phone": "821-689-5277", + "street": "9257 Upham Drive", + "city": "Portland", + "relatedContactIds": [ + "94", + "88", + "83", + "23", + "18", + "32", + "77", + "44", + "17", + "94", + "9", + "78", + "40", + "70", + "6", + "86", + "77", + "67", + "81", + "71" + ] + }, + { + "id": "92", + "firstname": "Ax", + "lastname": "Medler", + "email": "amedler2j@cdc.gov", + "phone": "292-637-3497", + "street": "5 Spaight Center", + "city": "Portland", + "relatedContactIds": [ + "88", + "45", + "6", + "84", + "59", + "31", + "3", + "40", + "100", + "81", + "79", + "42", + "100", + "16", + "23", + "79", + "24", + "51", + "17", + "71", + "70", + "4", + "88", + "38", + "81" + ] + }, + { + "id": "93", + "firstname": "Jehanna", + "lastname": "Groom", + "email": "jgroom2k@rediff.com", + "phone": "805-530-8235", + "street": "8 Scott Junction", + "city": "Portland", + "relatedContactIds": [ + "58", + "87", + "30", + "24", + "74", + "74", + "39", + "2", + "91", + "24", + "43", + "32", + "20", + "23", + "40" + ] + }, + { + "id": "94", + "firstname": "Jerry", + "lastname": "Khristyukhin", + "email": "jkhristyukhin2l@cocolog-nifty.com", + "phone": "341-952-1933", + "street": "20605 Ramsey Avenue", + "city": "Portland", + "relatedContactIds": [ + "8", + "27", + "70", + "55", + "2", + "79", + "22", + "2", + "71", + "72", + "35", + "81", + "49", + "55", + "38", + "46", + "93", + "69", + "46", + "25", + "98", + "97", + "39", + "15", + "2", + "70", + "4", + "54", + "43", + "73" + ] + }, + { + "id": "95", + "firstname": "Germaine", + "lastname": "Sumsion", + "email": "gsumsion2m@timesonline.co.uk", + "phone": "939-932-7402", + "street": "0646 Sage Trail", + "city": "Portland", + "relatedContactIds": [ + "1", + "90", + "68", + "35", + "68", + "94", + "11", + "81", + "54", + "57", + "53", + "15", + "62", + "93", + "47", + "67", + "15", + "47", + "83", + "6", + "14", + "96", + "83", + "20", + "88", + "100", + "19", + "31", + "54" + ] + }, + { + "id": "96", + "firstname": "Florri", + "lastname": "Brunke", + "email": "fbrunke2n@ezinearticles.com", + "phone": "445-575-4372", + "street": "0502 Veith Avenue", + "city": "Las Vegas", + "relatedContactIds": [ + "58", + "84", + "51", + "65", + "50", + "60", + "81", + "10", + "70", + "72", + "31", + "63", + "16", + "20", + "62", + "72", + "85", + "81", + "27", + "34", + "54", + "79", + "98", + "75", + "71" + ] + }, + { + "id": "97", + "firstname": "Gonzalo", + "lastname": "Aland", + "email": "galand2o@washington.edu", + "phone": "742-931-4232", + "street": "6022 Lighthouse Bay Terrace", + "city": "Las Vegas", + "relatedContactIds": [ + "31" + ] + }, + { + "id": "98", + "firstname": "Birgit", + "lastname": "Finnan", + "email": "bfinnan2p@cbsnews.com", + "phone": "663-848-9691", + "street": "63026 Karstens Alley", + "city": "Las Vegas", + "relatedContactIds": [ + "84", + "91", + "68", + "22", + "11", + "76", + "100", + "95", + "1" + ] + }, + { + "id": "99", + "firstname": "Sibyl", + "lastname": "McGillacoell", + "email": "smcgillacoell2q@tumblr.com", + "phone": "444-668-4955", + "street": "329 Merrick Hill", + "city": "Las Vegas", + "relatedContactIds": [ + "54", + "61", + "92", + "76", + "25" + ] + }, + { + "id": "100", + "firstname": "Charmian", + "lastname": "McLurg", + "email": "cmclurg2r@cnet.com", + "phone": "821-884-8382", + "street": "52531 Union Court", + "city": "Las Vegas", + "relatedContactIds": [ + "15", + "8", + "46", + "19", + "72", + "24", + "94" + ] + } +] diff --git a/projects/e2e/workbench-application-platform/contact-app/src/assets/manifest.json b/projects/e2e/workbench-application-platform/contact-app/src/assets/manifest.json new file mode 100644 index 000000000..08437d0e9 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/assets/manifest.json @@ -0,0 +1,78 @@ +{ + "name": "Contact Application", + "baseUrl": "#", + "capabilities": [ + { + "type": "activity", + "qualifier": { + "entity": "contacts" + }, + "description": "Lists all contacts and allows to show their personal data.", + "properties": { + "title": "Contacts", + "itemText": "person_outline", + "itemCssClass": "material-icons", + "cssClass": "e2e-contact-list", + "path": "contact/list" + } + }, + { + "type": "view", + "qualifier": { + "entity": "contact", + "id": "*" + }, + "description": "Shows personal data of a contact.", + "properties": { + "path": "contact/:id", + "title": "Contact", + "cssClass": "e2e-contact" + } + }, + { + "type": "popup", + "qualifier": { + "entity": "contact", + "action": "create" + }, + "description": "Allows to create a new contact.", + "properties": { + "path": "contact/new", + "width": "400px", + "height": "390px" + } + }, + { + "type": "popup", + "qualifier": { + "entity": "contact", + "id": "*", + "action": "add-related-contact" + }, + "description": "Allows to add another contact as related person.", + "properties": { + "path": "contact/:id/add-related-contact", + "width": "350px", + "height": "600px" + } + } + ], + "intents": [ + { + "type": "view", + "qualifier": { + "entity": "communication", + "presentation": "list", + "contactId": "*" + } + }, + { + "type": "popup", + "qualifier": { + "entity": "communication", + "action": "create", + "contactId": "*" + } + } + ] +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/environments/environment.now.ts b/projects/e2e/workbench-application-platform/contact-app/src/environments/environment.now.ts new file mode 100644 index 000000000..dc39aca58 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/environments/environment.now.ts @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +export const environment = { + production: true +}; diff --git a/projects/e2e/workbench-application-platform/contact-app/src/environments/environment.ts b/projects/e2e/workbench-application-platform/contact-app/src/environments/environment.ts new file mode 100644 index 000000000..2a785475f --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/environments/environment.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --configuration=now` replaces `environment.ts` with `environment.now.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/projects/e2e/workbench-application-platform/contact-app/src/index.html b/projects/e2e/workbench-application-platform/contact-app/src/index.html new file mode 100644 index 000000000..442f7103a --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/index.html @@ -0,0 +1,12 @@ + + + + + Contact Application + + + + + + + diff --git a/projects/e2e/workbench-application-platform/contact-app/src/main.ts b/projects/e2e/workbench-application-platform/contact-app/src/main.ts new file mode 100644 index 000000000..c7b673cf4 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/main.ts @@ -0,0 +1,12 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/projects/e2e/workbench-application-platform/contact-app/src/now.json b/projects/e2e/workbench-application-platform/contact-app/src/now.json new file mode 100644 index 000000000..7f9d9a3eb --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/now.json @@ -0,0 +1,5 @@ +{ + "version": 2, + "name": "scion-workbench-application-platform-contact", + "alias": "scion-workbench-application-platform-contact" +} diff --git a/projects/e2e/workbench-application-platform/contact-app/src/polyfills.ts b/projects/e2e/workbench-application-platform/contact-app/src/polyfills.ts new file mode 100644 index 000000000..f56cc3de6 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/polyfills.ts @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +import 'core-js/es6/symbol'; +import 'core-js/es6/object'; +import 'core-js/es6/function'; +import 'core-js/es6/parse-int'; +import 'core-js/es6/parse-float'; +import 'core-js/es6/number'; +import 'core-js/es6/math'; +import 'core-js/es6/string'; +import 'core-js/es6/date'; +import 'core-js/es6/array'; +import 'core-js/es6/regexp'; +import 'core-js/es6/map'; +import 'core-js/es6/weak-map'; +import 'core-js/es6/set'; +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + +/** + * If the application will be indexed by Google Search, the following is required. + * Googlebot uses a renderer based on Chrome 41. + * https://developers.google.com/search/docs/guides/rendering + **/ +// import 'core-js/es6/array'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following for the Reflect API. */ +// import 'core-js/es6/reflect'; + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + */ + + // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + + /* + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + */ +// (window as any).__Zone_enable_cross_context_check = true; + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/projects/e2e/workbench-application-platform/contact-app/src/styles.scss b/projects/e2e/workbench-application-platform/contact-app/src/styles.scss new file mode 100644 index 000000000..1506f1627 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/src/styles.scss @@ -0,0 +1,3 @@ +@import 'common'; + +@include app-theme(); diff --git a/projects/e2e/workbench-application-platform/contact-app/tsconfig.app.json b/projects/e2e/workbench-application-platform/contact-app/tsconfig.app.json new file mode 100644 index 000000000..0bc50df29 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/tsconfig.app.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../../out-tsc/contact-app", + "types": [] + } +} diff --git a/projects/e2e/workbench-application-platform/contact-app/tslint.json b/projects/e2e/workbench-application-platform/contact-app/tslint.json new file mode 100644 index 000000000..e3e7b64d8 --- /dev/null +++ b/projects/e2e/workbench-application-platform/contact-app/tslint.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ] + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/browserslist b/projects/e2e/workbench-application-platform/dev-tools-app/browserslist new file mode 100644 index 000000000..37371cb04 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/browserslist @@ -0,0 +1,11 @@ +# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed + +> 0.5% +last 2 versions +Firefox ESR +not dead +not IE 9-11 \ No newline at end of file diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app-routing.module.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app-routing.module.ts new file mode 100644 index 000000000..afa3ac21e --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app-routing.module.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + {path: 'dev-tools', loadChildren: './dev-tools/dev-tools.module#DevToolsModule'}, +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes, {useHash: true})], + exports: [RouterModule] +}) +export class AppRoutingModule { +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app.component.html b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app.component.html new file mode 100644 index 000000000..8fe45de6e --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app.component.html @@ -0,0 +1,3 @@ + + + diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app.component.scss b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app.component.scss new file mode 100644 index 000000000..e1f2859cf --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app.component.scss @@ -0,0 +1,13 @@ +@import 'common'; + +:host { + @include position(absolute, 0, 0, 0, 0); + + > sci-viewport { + @include position(absolute, 0, 0, 0, 0); + + router-outlet { + position: absolute; // take router outlet out of the document flow + } + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app.component.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app.component.ts new file mode 100644 index 000000000..15f0590f2 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app.component.ts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent { +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app.module.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app.module.ts new file mode 100644 index 000000000..49f5b608e --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/app.module.ts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; + +import { AppComponent } from './app.component'; +import { WorkbenchApplicationModule } from '@scion/workbench-application.angular'; +import { SciViewportModule } from '@scion/viewport'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { AppRoutingModule } from './app-routing.module'; + +@NgModule({ + declarations: [ + AppComponent, + ], + imports: [ + BrowserModule, + WorkbenchApplicationModule.forRoot(), + SciViewportModule, + AppRoutingModule, + BrowserAnimationsModule, + ], + providers: [], + bootstrap: [ + AppComponent, + ] +}) +export class AppModule { +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list-item/application-list-item.component.html b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list-item/application-list-item.component.html new file mode 100644 index 000000000..a9a88ee59 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list-item/application-list-item.component.html @@ -0,0 +1,8 @@ +{{manifest.name}} +{{manifest.symbolicName}} +{{manifest.baseUrl}} + +
+ {{intentCount}} {{intentCount === 1 ? 'intent' : 'intents'}} + {{capabilityCount}} {{capabilityCount === 1 ? 'capability' : 'capabilities'}} +
diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list-item/application-list-item.component.scss b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list-item/application-list-item.component.scss new file mode 100644 index 000000000..d9257d144 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list-item/application-list-item.component.scss @@ -0,0 +1,40 @@ +@import 'common'; + +:host { + display: grid; + grid-template-columns: auto auto; + grid-column-gap: 1em; + grid-row-gap: .5em; + grid-template-rows: auto; + align-items: start; + + > a.app-name { + grid-column: 1/2; + grid-row: 1/2; + font-weight: bold; + } + + > span.app-symbolic-name { + grid-column: 1/2; + grid-row: 2/3; + font-size: smaller; + } + + > a.url { + grid-column: 1/3; + grid-row: 3/4; + font-size: smaller; + } + + > div.chips { + grid-column: 2/3; + grid-row: 1/3; + display: flex; + flex-flow: row wrap; + justify-content: flex-end; + + > span.chip { + @include chip(accentColor(.2), transparent, accentColor(.75)); + } + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list-item/application-list-item.component.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list-item/application-list-item.component.ts new file mode 100644 index 000000000..8141f6493 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list-item/application-list-item.component.ts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { Manifest } from '@scion/workbench-application.core'; + +@Component({ + selector: 'app-application-list-item', + templateUrl: './application-list-item.component.html', + styleUrls: ['./application-list-item.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ApplicationListItemComponent { + + @Input() + public manifest: Manifest; + + public get intentCount(): number { + return this.manifest.intents.length; + } + + public get capabilityCount(): number { + return this.manifest.capabilities.length; + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.html b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.html new file mode 100644 index 000000000..213303c39 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.html @@ -0,0 +1,5 @@ + + + + + diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.scss b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.scss new file mode 100644 index 000000000..57bda86d3 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.scss @@ -0,0 +1,10 @@ +@import 'common'; + +:host { + display: flex; + padding: app-padding(); + + > sci-list { + flex: auto; + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.ts new file mode 100644 index 000000000..cfe299814 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-list/application-list.component.ts @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, TrackByFunction } from '@angular/core'; +import { BehaviorSubject, combineLatest, MonoTypeOperatorFunction, Observable, OperatorFunction } from 'rxjs'; +import { Manifest, ManifestRegistryService } from '@scion/workbench-application.core'; +import { map } from 'rxjs/operators'; +import { toFilterRegExp } from '@scion/e2e/common'; + +@Component({ + selector: 'app-application-list', + templateUrl: './application-list.component.html', + styleUrls: ['./application-list.component.scss'], +}) +export class ApplicationListComponent { + + private _filter$ = new BehaviorSubject(null); + + public manifests$: Observable; + + constructor(private _manifestRegistryService: ManifestRegistryService) { + this.manifests$ = combineLatest(this._filter$, this._manifestRegistryService.manifests$) + .pipe( + filterManifests(), + sortManifests() + ); + } + + public onFilter(filterText: string): void { + this._filter$.next(filterText); + } + + public trackByFn: TrackByFunction = (index: number, manifest: Manifest): any => { + return manifest.symbolicName; + }; +} + +function filterManifests(): OperatorFunction<[string, Manifest[]], Manifest[]> { + return map(([filter, manifests]: [string, Manifest[]]): Manifest[] => { + if (!filter) { + return manifests; + } + + const filterRegExp = toFilterRegExp(filter); + return manifests.filter(manifest => filterRegExp.test(manifest.name) || filterRegExp.test(manifest.symbolicName)); + }); +} + +function sortManifests(): MonoTypeOperatorFunction { + return map((manifests: Manifest[]): Manifest[] => [...manifests].sort((mf1, mf2) => mf1.name.localeCompare(mf2.name))); +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-view/application-view.component.html b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-view/application-view.component.html new file mode 100644 index 000000000..d6eb4e125 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-view/application-view.component.html @@ -0,0 +1,66 @@ +
+ + {{manifest?.name}} + + + {{manifest?.symbolicName}} + + + {{manifest?.baseUrl}} + + + + {{manifest.manifestUrl}} + + + + SCOPE CHECK DISABLED + +
+ +
+ +
+

Capabilities{{capabilities.length}}

+ + + + + + + + + + + + +
+ + +
+
+
+
+
+ + +
+

Intents{{intents.length}}

+ + + + + + + + + + + + +
+
diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-view/application-view.component.scss b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-view/application-view.component.scss new file mode 100644 index 000000000..ddca98d85 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-view/application-view.component.scss @@ -0,0 +1,134 @@ +@import 'common'; + +:host { + display: flex; + flex-direction: column; + padding: app-padding(); + + > section.application { + flex: none; + display: grid; + grid-template-columns: minmax(80px, 120px) auto auto; + grid-column-gap: 1em; + grid-row-gap: .5em; + grid-template-rows: auto; + margin-bottom: 1.5em; + + border: 1px solid accentColor(.25); + border-radius: 5px; + padding: 1em; + @include grid-container-align-items(center); + + > label.app-name { + grid-column: 1/2; + grid-row: 1/2; + } + + > span.app-name { + grid-column: 2/3; + grid-row: 1/2; + } + + > label.app-symbolic-name { + grid-column: 1/2; + grid-row: 2/3; + } + + > span.app-symbolic-name { + grid-column: 2/3; + grid-row: 2/3; + } + + > label.app-url { + grid-column: 1/2; + grid-row: 3/4; + } + + > a.app-url { + grid-column: 2/3; + grid-row: 3/4; + } + + > label.manifest-url { + grid-column: 1/2; + grid-row: 4/5; + } + + > a.manifest-url { + grid-column: 2/3; + grid-row: 4/5; + } + + > span.scope-check-disabled { + grid-column: 3/4; + grid-row: 1/5; + align-self: start; + justify-self: end; + font-weight: bold; + @include chip(rgba(255, 0, 0, .75), rgba(255, 0, 0, .1), rgba(255, 0, 0, .75)); + margin: 0; + } + } + + > section.sashbox { + flex: auto; + display: flex; + flex-direction: column; + + > section.sash-1, > section.sash-2 { + flex: 1 1 0; + overflow: hidden; + display: flex; + flex-direction: column; + + > h2 { + margin-top: 0; + font-size: 1.25em; + position: relative; // positioning anchor for the badge + align-self: flex-start; + + > span.count-badge { + $size: 12px; + position: absolute; + display: flex; + align-items: center; + justify-content: center; + right: -1.5em; + top: 0; + border-radius: $size/2; + color: #FFFFFF; + font-size: 10px; + font-weight: normal; + min-height: $size; + min-width: $size; + background-color: accentColor(.4); + user-select: none; + } + } + + > sci-filter-field { + margin-bottom: .2em; + } + + > sci-accordion { + flex: auto; + } + } + + > div.splitter { + flex: 0 0 auto; + margin: .5em 0; + width: 20px; + align-self: center; + + > div.sash-indicator { + background: accentColor(); + height: 1px; + + &:not(:first-child) { + margin-top: 1px; + } + } + } + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-view/application-view.component.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-view/application-view.component.ts new file mode 100644 index 000000000..6a38d57fb --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/application-view/application-view.component.ts @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, ElementRef, OnDestroy, ViewChild } from '@angular/core'; +import { provideWorkbenchView, WorkbenchView } from '@scion/workbench-application.angular'; +import { ActivatedRoute } from '@angular/router'; +import { BehaviorSubject, combineLatest, Observable, of, OperatorFunction, Subject } from 'rxjs'; +import { distinctUntilChanged, filter, map, switchMap, takeUntil, tap } from 'rxjs/operators'; +import { Capability, Intent, Manifest, ManifestRegistryService, NotificationService } from '@scion/workbench-application.core'; +import { toFilterRegExp } from '@scion/e2e/common'; + +@Component({ + selector: 'app-application-view', + templateUrl: './application-view.component.html', + styleUrls: ['./application-view.component.scss'], + providers: [ + provideWorkbenchView(ApplicationViewComponent) + ] +}) +export class ApplicationViewComponent implements OnDestroy { + + private _destroy$ = new Subject(); + + public manifest: Manifest; + public capabilities$: Observable; + public intents$: Observable; + + public sashPositionFr = .5; + + private _capabilityFilter$ = new BehaviorSubject(null); + private _intentFilter$ = new BehaviorSubject(null); + + @ViewChild('sashbox') + private _sashbox: ElementRef; + + constructor(route: ActivatedRoute, + notificationService: NotificationService, + view: WorkbenchView, + private _manifestRegistryService: ManifestRegistryService) { + route.params + .pipe( + map(params => params['symbolicName']), + distinctUntilChanged(), + switchMap(symbolicName => this._manifestRegistryService.manifest$(symbolicName)), + tap(manifest => !manifest && notificationService.notify({ + severity: 'error', + title: 'Application not found', + text: `No application found with given symbolic name '${route.snapshot.params['symbolicName']}'` + })), + filter(Boolean), + takeUntil(this._destroy$) + ) + .subscribe((manifest: Manifest) => { + const capabilities: Capability[] = [...manifest.capabilities].sort((c1, c2) => c1.type.localeCompare(c2.type)); + const intents: Intent[] = [...manifest.intents].sort((i1, it2) => i1.type.localeCompare(it2.type)); + + view.title = manifest.name; + view.heading = 'Application Manifest'; + + this.manifest = manifest; + this.capabilities$ = combineLatest(this._capabilityFilter$, of(capabilities)).pipe(filterCapabilities()); + this.intents$ = combineLatest(this._intentFilter$, of(intents)).pipe(filterIntents()); + }); + } + + public onSash(deltaPx: number): void { + const sashboxHeightPx = this._sashbox.nativeElement.clientHeight; + const sashPositionFr = this.sashPositionFr + (deltaPx / sashboxHeightPx); + this.sashPositionFr = Math.min(1, Math.max(0, sashPositionFr)); + } + + public onCapabilityFilter(filterText: string): void { + this._capabilityFilter$.next(filterText); + } + + public onIntentFilter(filterText: string): void { + this._intentFilter$.next(filterText); + } + + public onSashReset(): void { + this.sashPositionFr = .5; + } + + public ngOnDestroy(): void { + this._destroy$.next(); + } +} + +function filterCapabilities(): OperatorFunction<[string, Capability[]], Capability[]> { + return map(([filterText, capabilities]: [string, Capability[]]): Capability[] => { + if (!filterText) { + return capabilities; + } + + const filterRegExp = toFilterRegExp(filterText); + return capabilities.filter(capability => ( + filterRegExp.test(capability.type) || + filterRegExp.test(capability.private ? 'private' : 'public') || + Object.keys(capability.qualifier || {}).some(key => filterRegExp.test(key)) || + Object.values(capability.qualifier || {}).some(value => filterRegExp.test(`${value}`))) + ); + }); +} + +function filterIntents(): OperatorFunction<[string, Intent[]], Intent[]> { + return map(([filterText, intents]: [string, Intent[]]): Intent[] => { + if (!filterText) { + return intents; + } + + const filterRegExp = toFilterRegExp(filterText); + return intents.filter(intent => ( + filterRegExp.test(intent.type) || + filterRegExp.test(intent.metadata.implicit ? 'implicit' : 'explicit') || + Object.keys(intent.qualifier || {}).some(key => filterRegExp.test(key)) || + Object.values(intent.qualifier || {}).some(value => filterRegExp.test(`${value}`))) + ); + }); +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-item/capability-accordion-item.component.html b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-item/capability-accordion-item.component.html new file mode 100644 index 000000000..28f358f0a --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-item/capability-accordion-item.component.html @@ -0,0 +1,16 @@ + +
+ + PUBLIC + + + PRIVATE + + +
diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-item/capability-accordion-item.component.scss b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-item/capability-accordion-item.component.scss new file mode 100644 index 000000000..8a0adc136 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-item/capability-accordion-item.component.scss @@ -0,0 +1,28 @@ +@import 'common'; + +:host { + display: grid; + justify-content: space-between; + @include grid-container-align-items(center); + grid-template-columns: auto auto; + grid-template-rows: auto; + grid-column-gap: 1em; + + > div.actions { + display: flex; + flex-direction: column; + align-items: flex-end; + + > span.scope { + font-weight: bold; + @include chip(primaryColor(), null, primaryColor()); + margin: 0; + } + + > button.execute { + &:not(:first-child) { + margin-top: .5em; + } + } + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-item/capability-accordion-item.component.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-item/capability-accordion-item.component.ts new file mode 100644 index 000000000..44230022c --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-item/capability-accordion-item.component.ts @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core'; +import { Capability, ManifestRegistryService, PlatformCapabilityTypes, Popup, PopupService, Qualifier } from '@scion/workbench-application.core'; +import { map, switchMap, takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'app-capability-accordion-item', + templateUrl: './capability-accordion-item.component.html', + styleUrls: ['./capability-accordion-item.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CapabilityAccordionItemComponent implements OnChanges, OnDestroy { + + private _destroy$ = new Subject(); + private _inputChange$ = new Subject(); + + public execQualifier: Qualifier; + + @Input() + public capability: Capability; + + constructor(private _popupService: PopupService, + manifestRegistryService: ManifestRegistryService, + cd: ChangeDetectorRef) { + this._inputChange$ + .pipe( + switchMap(() => manifestRegistryService.capabilities$(PlatformCapabilityTypes.Popup, this.createExecQualifier())), + map((capabilities: Capability[]) => capabilities.length > 0), + takeUntil(this._destroy$), + ) + .subscribe((executable: boolean) => { + this.execQualifier = (executable ? this.createExecQualifier() : null); + cd.markForCheck(); + }); + } + + public ngOnChanges(changes: SimpleChanges): void { + this._inputChange$.next(); + } + + public onCapabilityExecute(event: MouseEvent): void { + if (!this.execQualifier) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + const popup: Popup = { + position: 'west', + anchor: event.target as Element, + }; + + this._popupService.open(popup, this.execQualifier); + } + + private createExecQualifier(): Qualifier { + return { + entity: 'capability', + action: 'execute', + type: this.capability.type, + capabilityId: this.capability.metadata.id, + }; + } + + public ngOnDestroy(): void { + this._destroy$.next(); + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-panel/capability-accordion-panel.component.html b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-panel/capability-accordion-panel.component.html new file mode 100644 index 000000000..c5410653c --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-panel/capability-accordion-panel.component.html @@ -0,0 +1,21 @@ +
+ {{capability.description}} +
+ +
+

Properties

+ +
+ +
+

Used by following applications

+ +
diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-panel/capability-accordion-panel.component.scss b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-panel/capability-accordion-panel.component.scss new file mode 100644 index 000000000..2b17e917c --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-panel/capability-accordion-panel.component.scss @@ -0,0 +1,44 @@ +@import 'common'; + +:host { + display: grid; + grid-template-columns: auto auto; + grid-column-gap: 1em; + grid-row-gap: .5em; + grid-template-rows: auto; + + > section.description { + grid-column: 1/3; + font-style: italic; + } + + > section { + border: 1px solid accentColor(.25); + border-radius: 5px; + padding: 1em; + background-color: #FFFFFF; + font-size: 12px; + + > h2 { + font-size: 1.1em; + margin-top: 0; + margin-bottom: 1em; + } + + > ul { + list-style: none; + margin: 0; + padding: 0; + + > li > a.self { + font-style: italic; + } + } + } + + &:not(.has-properties) { + > section.used-by { + grid-column: 1/3; + } + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-panel/capability-accordion-panel.component.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-panel/capability-accordion-panel.component.ts new file mode 100644 index 000000000..e36dba748 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/capability-accordion-panel/capability-accordion-panel.component.ts @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { ChangeDetectionStrategy, Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Application, Capability, ManifestRegistryService } from '@scion/workbench-application.core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'app-capability-accordion-panel', + templateUrl: './capability-accordion-panel.component.html', + styleUrls: ['./capability-accordion-panel.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CapabilityAccordionPanelComponent implements OnChanges { + + public consumers$: Observable; + + @Input() + public capability: Capability; + + @HostBinding('class.has-properties') + public hasProperties: boolean; + + constructor(private _manifestRegistryService: ManifestRegistryService) { + } + + public ngOnChanges(changes: SimpleChanges): void { + this.hasProperties = Object.keys(this.capability.properties || {}).length > 0; + this.consumers$ = this._manifestRegistryService.capabilityConsumers$(this.capability.metadata.id) + .pipe(map(consumers => [...consumers].sort((c1, c2) => c1.name.localeCompare(c2.name)))); + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools-routing.module.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools-routing.module.ts new file mode 100644 index 000000000..022ea41f8 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools-routing.module.ts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { ApplicationListComponent } from './application-list/application-list.component'; +import { ApplicationViewComponent } from './application-view/application-view.component'; +import { OutletCapabilityExecPopupComponent } from './outlet-capability-exec-popup/outlet-capability-exec-popup.component'; + +const routes: Routes = [ + {path: 'application-list', component: ApplicationListComponent}, + {path: 'application/:symbolicName', component: ApplicationViewComponent}, + {path: 'view-capability/:id', component: OutletCapabilityExecPopupComponent}, + {path: 'popup-capability/:id', component: OutletCapabilityExecPopupComponent}, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class DevToolsRoutingModule { +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools.module.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools.module.ts new file mode 100644 index 000000000..20181d142 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/dev-tools.module.ts @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SciViewportModule } from '@scion/viewport'; +import { SciAccordionModule, SciFilterFieldModule, SciListModule, SciParamsEnterModule, SciPopupShellModule, SciPropertyModule, SciSashModule } from '@scion/e2e/common'; +import { DevToolsRoutingModule } from './dev-tools-routing.module'; +import { ReactiveFormsModule } from '@angular/forms'; +import { WorkbenchApplicationModule } from '@scion/workbench-application.angular'; +import { ApplicationListComponent } from './application-list/application-list.component'; +import { ApplicationListItemComponent } from './application-list-item/application-list-item.component'; +import { ApplicationViewComponent } from './application-view/application-view.component'; +import { CapabilityAccordionItemComponent } from './capability-accordion-item/capability-accordion-item.component'; +import { QualifierChipListComponent } from './qualifier-chip-list/qualifier-chip-list.component'; +import { IntentAccordionItemComponent } from './intent-accordion-item/intent-accordion-item.component'; +import { IntentAccordionPanelComponent } from './intent-accordion-panel/intent-accordion-panel.component'; +import { CapabilityAccordionPanelComponent } from './capability-accordion-panel/capability-accordion-panel.component'; +import { OutletCapabilityExecPopupComponent } from './outlet-capability-exec-popup/outlet-capability-exec-popup.component'; + +@NgModule({ + declarations: [ + ApplicationListComponent, + ApplicationListItemComponent, + ApplicationViewComponent, + CapabilityAccordionItemComponent, + CapabilityAccordionPanelComponent, + IntentAccordionItemComponent, + IntentAccordionPanelComponent, + QualifierChipListComponent, + OutletCapabilityExecPopupComponent, + ], + imports: [ + CommonModule, + SciViewportModule, + ReactiveFormsModule, + WorkbenchApplicationModule.forChild(), + DevToolsRoutingModule, + SciListModule, + SciAccordionModule, + SciSashModule, + SciFilterFieldModule, + SciPopupShellModule, + SciParamsEnterModule, + SciPropertyModule, + ], + exports: [], + providers: [] +}) +export class DevToolsModule { +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-item/intent-accordion-item.component.html b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-item/intent-accordion-item.component.html new file mode 100644 index 000000000..e3f6bf961 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-item/intent-accordion-item.component.html @@ -0,0 +1,5 @@ + +
+ IMPLICIT + NOT HANDLED +
diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-item/intent-accordion-item.component.scss b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-item/intent-accordion-item.component.scss new file mode 100644 index 000000000..727e0abda --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-item/intent-accordion-item.component.scss @@ -0,0 +1,26 @@ +@import 'common'; + +:host { + display: grid; + justify-content: space-between; + @include grid-container-align-items(center); + grid-template-columns: auto auto; + grid-template-rows: auto; + grid-column-gap: 1em; + + > div.hints { + display: flex; + flex-direction: column; + align-items: flex-end; + + > span.implicit { + font-weight: bold; + @include chip(primaryColor(), null, primaryColor(), dotted); + } + + > span.not-handled { + font-weight: bold; + @include chip(rgba(255, 0, 0, .75), rgba(255, 0, 0, .1), rgba(255, 0, 0, .75)); + } + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-item/intent-accordion-item.component.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-item/intent-accordion-item.component.ts new file mode 100644 index 000000000..e1c4bb23f --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-item/intent-accordion-item.component.ts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Intent, ManifestRegistryService } from '@scion/workbench-application.core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'app-intent-accordion-item', + templateUrl: './intent-accordion-item.component.html', + styleUrls: ['./intent-accordion-item.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class IntentAccordionItemComponent implements OnChanges { + + public anyQualifier: boolean; + public unhandled$: Observable; + + @Input() + public intent: Intent; + + constructor(private _manifestRegistryService: ManifestRegistryService) { + } + + public ngOnChanges(changes: SimpleChanges): void { + this.anyQualifier = Object.keys(this.intent.qualifier || {}).includes('*'); + + this.unhandled$ = this._manifestRegistryService.capabilityProviders$(this.intent.metadata.id) + .pipe(map(providers => providers.length === 0)); + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-panel/intent-accordion-panel.component.html b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-panel/intent-accordion-panel.component.html new file mode 100644 index 000000000..6590aa9fd --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-panel/intent-accordion-panel.component.html @@ -0,0 +1,12 @@ +
+

Handled by following applications

+ +
diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-panel/intent-accordion-panel.component.scss b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-panel/intent-accordion-panel.component.scss new file mode 100644 index 000000000..cfca1cc74 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-panel/intent-accordion-panel.component.scss @@ -0,0 +1,30 @@ +@import 'common'; + +:host { + display: flex; + + > section { + border: 1px solid accentColor(.25); + border-radius: 5px; + padding: 1em; + background-color: #FFFFFF; + font-size: 12px; + flex: auto; + + > h2 { + font-size: 1em; + margin-top: 0; + margin-bottom: 1em; + } + + > ul { + list-style: none; + margin: 0; + padding: 0; + + > li > a.self { + font-style: italic; + } + } + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-panel/intent-accordion-panel.component.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-panel/intent-accordion-panel.component.ts new file mode 100644 index 000000000..4e683ec1a --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/intent-accordion-panel/intent-accordion-panel.component.ts @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Application, Intent, ManifestRegistryService } from '@scion/workbench-application.core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'app-intent-accordion-panel', + templateUrl: './intent-accordion-panel.component.html', + styleUrls: ['./intent-accordion-panel.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class IntentAccordionPanelComponent implements OnChanges { + + public anyQualifier: boolean; + public providers$: Observable; + + @Input() + public intent: Intent; + + constructor(private _manifestRegistryService: ManifestRegistryService) { + } + + public ngOnChanges(changes: SimpleChanges): void { + this.anyQualifier = Object.keys(this.intent.qualifier || {}).includes('*'); + this.providers$ = this._manifestRegistryService.capabilityProviders$(this.intent.metadata.id) + .pipe(map(providers => [...providers].sort((p1, p2) => p1.name.localeCompare(p2.name)))); + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/outlet-capability-exec-popup/outlet-capability-exec-popup.component.html b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/outlet-capability-exec-popup/outlet-capability-exec-popup.component.html new file mode 100644 index 000000000..7d267eb5e --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/outlet-capability-exec-popup/outlet-capability-exec-popup.component.html @@ -0,0 +1,13 @@ + + Execute {{capability?.type}} capability + + +
+ + + +
+
+ + Execute +
diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/outlet-capability-exec-popup/outlet-capability-exec-popup.component.scss b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/outlet-capability-exec-popup/outlet-capability-exec-popup.component.scss new file mode 100644 index 000000000..786446fba --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/outlet-capability-exec-popup/outlet-capability-exec-popup.component.scss @@ -0,0 +1,21 @@ +:host { + display: flex; + flex-direction: column; + + > sci-popup-shell { + flex: auto; + + main { + display: flex; + flex-direction: column; + + > sci-params-enter { + flex: none; + + &:not(:last-child) { + margin-bottom: 1em; + } + } + } + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/outlet-capability-exec-popup/outlet-capability-exec-popup.component.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/outlet-capability-exec-popup/outlet-capability-exec-popup.component.ts new file mode 100644 index 000000000..1611ee8cc --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/outlet-capability-exec-popup/outlet-capability-exec-popup.component.ts @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, OnDestroy } from '@angular/core'; +import { provideWorkbenchPopup, WorkbenchPopup, WorkbenchRouter } from '@scion/workbench-application.angular'; +import { ActivatedRoute, Params } from '@angular/router'; +import { Capability, ManifestRegistryService, NotificationService, PlatformCapabilityTypes, PopupService, Qualifier } from '@scion/workbench-application.core'; +import { Subject } from 'rxjs'; +import { distinctUntilChanged, filter, map, switchMap, takeUntil, tap } from 'rxjs/operators'; +import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { FocusTrapFactory } from '@angular/cdk/a11y'; +import { PARAM_NAME, PARAM_VALUE, SciParamsEnterComponent } from '@scion/e2e/common'; + +const QUALIFIER = 'qualifier'; +const QUERY_PARAMS = 'queryParams'; +const MATRIX_PARAMS = 'matrixParams'; + +@Component({ + selector: 'app-outlet-capability-exec-popup', + templateUrl: './outlet-capability-exec-popup.component.html', + styleUrls: ['./outlet-capability-exec-popup.component.scss'], + providers: [ + provideWorkbenchPopup(OutletCapabilityExecPopupComponent) + ] +}) +export class OutletCapabilityExecPopupComponent implements OnDestroy { + + public readonly QUALIFIER = QUALIFIER; + public readonly QUERY_PARAMS = QUERY_PARAMS; + public readonly MATRIX_PARAMS = MATRIX_PARAMS; + + public capability: Capability; + public form: FormGroup; + public valid: boolean; + + private _destroy$ = new Subject(); + + constructor(route: ActivatedRoute, + manifestRegistryService: ManifestRegistryService, + notificationService: NotificationService, + formBuilder: FormBuilder, + private _popup: WorkbenchPopup, + private _focusTrapFactory: FocusTrapFactory, + private _workbenchRouter: WorkbenchRouter, + private _popupService: PopupService) { + this.form = formBuilder.group({ + [QUALIFIER]: formBuilder.array([]), + [QUERY_PARAMS]: formBuilder.array([]), + [MATRIX_PARAMS]: formBuilder.array([]), + }); + + route.params + .pipe( + map(params => params['id']), + distinctUntilChanged(), + switchMap(capabilityId => manifestRegistryService.capability$(capabilityId)), + tap(manifest => !manifest && notificationService.notify({ + severity: 'error', + title: 'Capability not found', + text: `No capability found with given id '${route.snapshot.params['id']}'` + })), + filter(Boolean), + takeUntil(this._destroy$), + ) + .subscribe(capability => { + this.capability = capability; + this.createQualifierFormControls(capability.qualifier, formBuilder); + this.requestFocus(); + }); + } + + private createQualifierFormControls(qualifier: Qualifier, formBuilder: FormBuilder): void { + const formArray = formBuilder.array([]); + Object.entries(qualifier).forEach(([key, value]) => { + const readonly = (value !== '*'); + formArray.push(formBuilder.group({ + [PARAM_NAME]: formBuilder.control({value: key, disabled: true}), + [PARAM_VALUE]: formBuilder.control({value: (readonly ? value : ''), disabled: readonly}, Validators.required), + })); + }); + this.form.setControl(QUALIFIER, formArray); + } + + private requestFocus(): void { + const focusTrap = this._focusTrapFactory.create(document.body); + focusTrap.focusInitialElementWhenReady().finally(() => focusTrap.destroy()); + } + + public onExecute(): void { + const qualifier: Qualifier = SciParamsEnterComponent.toParams(this.form.get(QUALIFIER) as FormArray); + const queryParams: Params = SciParamsEnterComponent.toParams(this.form.get(QUERY_PARAMS) as FormArray); + const matrixParams: Params = SciParamsEnterComponent.toParams(this.form.get(MATRIX_PARAMS) as FormArray); + + switch (this.capability.type) { + case PlatformCapabilityTypes.View: { + this._workbenchRouter.navigate(qualifier, { + target: 'blank', + matrixParams, + queryParams + }); + break; + } + case PlatformCapabilityTypes.Popup: { + this._popupService.open({ + position: 'east', + anchor: event.target as Element, + queryParams: queryParams, + matrixParams: matrixParams + }, qualifier); + break; + } + default: { + throw Error(`[CapabilityNotHandledError] Capability type not handled [type=${this.capability.type}]`); + } + } + + this._popup.close(); + } + + public ngOnDestroy(): void { + this._destroy$.next(); + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/qualifier-chip-list/qualifier-chip-list.component.html b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/qualifier-chip-list/qualifier-chip-list.component.html new file mode 100644 index 000000000..965937a24 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/qualifier-chip-list/qualifier-chip-list.component.html @@ -0,0 +1,9 @@ +
    +
  • + {{type}} +
  • +
  • + {{entry.key}} + {{entry.value}} +
  • +
diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/qualifier-chip-list/qualifier-chip-list.component.scss b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/qualifier-chip-list/qualifier-chip-list.component.scss new file mode 100644 index 000000000..d0fadf1f3 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/qualifier-chip-list/qualifier-chip-list.component.scss @@ -0,0 +1,54 @@ +@import 'common'; + +:host { + + > ul { + list-style: none; + padding: 0; + margin: 0; + display: flex; + + > li { + @include chip(accentColor(.25), null, accentColor(1)); + + display: flex; + flex-direction: column; + align-items: center; + padding: .25em 1em; + + > span.key { + color: accentColor(.75) + } + + > span.value { + font-size: larger; + font-weight: bold; + color: accentColor(1) + } + + &::before { + display: block; + content: '«qualifier»'; + font-size: smaller; + margin-bottom: .75em; + color: accentColor(.5); + } + } + + > li.type { + @include chip(accentColor(.25), accentColor(.1), accentColor(1)); + min-width: 75px; + + > span.type { + font-size: 1.5em; + font-variant: small-caps; + font-weight: bold; + color: accentColor(1) + } + + &::before { + content: '«type»'; + } + } + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/qualifier-chip-list/qualifier-chip-list.component.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/qualifier-chip-list/qualifier-chip-list.component.ts new file mode 100644 index 000000000..056ef7440 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/app/dev-tools/qualifier-chip-list/qualifier-chip-list.component.ts @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Qualifier } from '@scion/workbench-application.core'; +import { KeyValue } from '@angular/common'; + +@Component({ + selector: 'app-qualifier-chip-list', + templateUrl: './qualifier-chip-list.component.html', + styleUrls: ['./qualifier-chip-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class QualifierChipListComponent implements OnChanges { + + private _qualifierKeys: string[]; + + @Input() + public qualifier: Qualifier; + + @Input() + public type: string; + + public ngOnChanges(changes: SimpleChanges): void { + this._qualifierKeys = Object.keys(this.qualifier || {}); + } + + /** + * Compares qualifier entries by their position in the object. + */ + public qualifierKeyCompareFn = (a: KeyValue, b: KeyValue): number => { + return this._qualifierKeys.indexOf(a.key) - this._qualifierKeys.indexOf(b.key); + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/assets/.gitkeep b/projects/e2e/workbench-application-platform/dev-tools-app/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/assets/manifest.json b/projects/e2e/workbench-application-platform/dev-tools-app/src/assets/manifest.json new file mode 100644 index 000000000..715973ec6 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/assets/manifest.json @@ -0,0 +1,97 @@ +{ + "name": "SCION Workbench Application Platform DevTools", + "baseUrl": "#", + "capabilities": [ + { + "type": "activity", + "qualifier": { + "entity": "dev-tools" + }, + "description": "Lists all registered applications and allows to show their manifests.", + "properties": { + "title": "DevTools", + "path": "dev-tools/application-list", + "itemText": "apps", + "itemCssClass": "material-icons", + "position": 9999999999 + } + }, + { + "type": "view", + "qualifier": { + "entity": "application-list" + }, + "private": false, + "description": "Lists all registered applications and allows to show their manifests.", + "properties": { + "path": "dev-tools/application-list", + "title": "Application manifests" + } + }, + { + "type": "view", + "qualifier": { + "entity": "application", + "symbolicName": "*" + }, + "private": false, + "description": "Shows the application manifest with all its capabilities, intents and dependent applications.", + "properties": { + "path": "dev-tools/application/:symbolicName", + "title": "Application Manifest" + } + }, + { + "type": "popup", + "qualifier": { + "entity": "capability", + "action": "execute", + "type": "view", + "capabilityId": "*" + }, + "private": false, + "description": "Allows to execute a capability of the type 'view'.", + "properties": { + "path": "dev-tools/view-capability/:capabilityId", + "width": "400px", + "height": "500px" + } + }, + { + "type": "popup", + "qualifier": { + "entity": "capability", + "action": "execute", + "type": "popup", + "capabilityId": "*" + }, + "private": false, + "description": "Allows to execute a capability of the type 'popup'.", + "properties": { + "path": "dev-tools/popup-capability/:capabilityId", + "width": "400px", + "height": "500px" + } + } + ], + "intents": [ + { + "type": "manifest-registry" + }, + { + "type": "notification" + }, + { + "type": "view", + "qualifier": { + "*": "*" + } + }, + { + "type": "popup", + "qualifier": { + "*": "*" + } + } + ] +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/environments/environment.now.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/environments/environment.now.ts new file mode 100644 index 000000000..dc39aca58 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/environments/environment.now.ts @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +export const environment = { + production: true +}; diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/environments/environment.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/environments/environment.ts new file mode 100644 index 000000000..2a785475f --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/environments/environment.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --configuration=now` replaces `environment.ts` with `environment.now.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/index.html b/projects/e2e/workbench-application-platform/dev-tools-app/src/index.html new file mode 100644 index 000000000..952fb2984 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/index.html @@ -0,0 +1,12 @@ + + + + + SCION Workbench Application Platform DevTools + + + + + + + diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/main.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/main.ts new file mode 100644 index 000000000..c7b673cf4 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/main.ts @@ -0,0 +1,12 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/now.json b/projects/e2e/workbench-application-platform/dev-tools-app/src/now.json new file mode 100644 index 000000000..a7c989237 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/now.json @@ -0,0 +1,5 @@ +{ + "version": 2, + "name": "scion-workbench-application-platform-devtools", + "alias": "scion-workbench-application-platform-devtools" +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/polyfills.ts b/projects/e2e/workbench-application-platform/dev-tools-app/src/polyfills.ts new file mode 100644 index 000000000..f56cc3de6 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/polyfills.ts @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +import 'core-js/es6/symbol'; +import 'core-js/es6/object'; +import 'core-js/es6/function'; +import 'core-js/es6/parse-int'; +import 'core-js/es6/parse-float'; +import 'core-js/es6/number'; +import 'core-js/es6/math'; +import 'core-js/es6/string'; +import 'core-js/es6/date'; +import 'core-js/es6/array'; +import 'core-js/es6/regexp'; +import 'core-js/es6/map'; +import 'core-js/es6/weak-map'; +import 'core-js/es6/set'; +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + +/** + * If the application will be indexed by Google Search, the following is required. + * Googlebot uses a renderer based on Chrome 41. + * https://developers.google.com/search/docs/guides/rendering + **/ +// import 'core-js/es6/array'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following for the Reflect API. */ +// import 'core-js/es6/reflect'; + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + */ + + // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + + /* + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + */ +// (window as any).__Zone_enable_cross_context_check = true; + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/src/styles.scss b/projects/e2e/workbench-application-platform/dev-tools-app/src/styles.scss new file mode 100644 index 000000000..1506f1627 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/src/styles.scss @@ -0,0 +1,3 @@ +@import 'common'; + +@include app-theme(); diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/tsconfig.app.json b/projects/e2e/workbench-application-platform/dev-tools-app/tsconfig.app.json new file mode 100644 index 000000000..2209158a9 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/tsconfig.app.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../../out-tsc/dev-tools-app", + "types": [] + } +} diff --git a/projects/e2e/workbench-application-platform/dev-tools-app/tslint.json b/projects/e2e/workbench-application-platform/dev-tools-app/tslint.json new file mode 100644 index 000000000..e3e7b64d8 --- /dev/null +++ b/projects/e2e/workbench-application-platform/dev-tools-app/tslint.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ] + } +} diff --git a/projects/e2e/workbench-application-platform/host-app/browserslist b/projects/e2e/workbench-application-platform/host-app/browserslist new file mode 100644 index 000000000..37371cb04 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/browserslist @@ -0,0 +1,11 @@ +# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed + +> 0.5% +last 2 versions +Firefox ESR +not dead +not IE 9-11 \ No newline at end of file diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/app.component.html b/projects/e2e/workbench-application-platform/host-app/src/app/app.component.html new file mode 100644 index 000000000..b73617382 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/app.component.html @@ -0,0 +1 @@ + diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/app.component.ts b/projects/e2e/workbench-application-platform/host-app/src/app/app.component.ts new file mode 100644 index 000000000..169b1f23e --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/app.component.ts @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html' +}) +export class AppComponent { +} diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/app.module.ts b/projects/e2e/workbench-application-platform/host-app/src/app/app.module.ts new file mode 100644 index 000000000..222c6f1f4 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/app.module.ts @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; + +import { AppComponent } from './app.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { WorkbenchModule } from '@scion/workbench'; +import { RouterModule } from '@angular/router'; +import { WorkbenchApplicationPlatformModule } from '@scion/workbench-application-platform'; +import { CustomExtensionModule } from './custom-extension/custom-extension.module'; +import { environment } from '../environments/environment'; + +@NgModule({ + declarations: [ + AppComponent, + ], + imports: [ + WorkbenchModule.forRoot(), + WorkbenchApplicationPlatformModule.forRoot({ + applicationConfig: [ + { + symbolicName: 'contact-app', + manifestUrl: environment.contact_app_manifest_url, + }, + { + symbolicName: 'communication-app', + manifestUrl: environment.communication_app_manifest_url, + }, + { + symbolicName: 'devtools-app', + manifestUrl: environment.devtools_app_manifest_url, + scopeCheckDisabled: true, + }, + { + symbolicName: 'testing-app', + exclude: !environment.testing_app_manifest_url, + manifestUrl: environment.testing_app_manifest_url, + }, + { + symbolicName: 'angular-io-app', + manifestUrl: '/assets/angular-io-manifest.json', + }, + ], + }), + RouterModule.forRoot([], { + useHash: true, + initialNavigation: 'disabled' + }), + BrowserAnimationsModule, + BrowserModule, + CustomExtensionModule, + ], + bootstrap: [ + AppComponent + ] +}) +export class AppModule { +} + diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-extension.module.ts b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-extension.module.ts new file mode 100644 index 000000000..270a91107 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-extension.module.ts @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ListMessageboxComponent } from './list-messagebox/list-messagebox.component'; +import { ACTIVITY_ACTION_PROVIDER, INTENT_HANDLER, MessageBoxIntentHandler, NotificationIntentHandler } from '@scion/workbench-application-platform'; +import { ListNotificationComponent } from './list-notification/list-notification.component'; +import { CustomNotifyActivityActionComponent } from './custom-notify-activity-action/custom-notify-activity-action.component'; +import { CustomNotifyActivityActionProvider } from './custom-notify-activity-action/custom-notify-activity-action-provider.service'; + +@NgModule({ + declarations: [ + ListMessageboxComponent, + ListNotificationComponent, + CustomNotifyActivityActionComponent, + ], + imports: [ + CommonModule, + ], + entryComponents: [ + ListMessageboxComponent, + ListNotificationComponent, + CustomNotifyActivityActionComponent, + ], + providers: [ + { + provide: INTENT_HANDLER, + useFactory: provideListMessageBoxIntentHandler, + multi: true, + }, + { + provide: INTENT_HANDLER, + useFactory: provideListNotificationIntentHandler, + multi: true, + }, + { + provide: ACTIVITY_ACTION_PROVIDER, + useClass: CustomNotifyActivityActionProvider, + multi: true + }, + ], +}) +export class CustomExtensionModule { +} + +export function provideListMessageBoxIntentHandler(): MessageBoxIntentHandler { + return new MessageBoxIntentHandler({'type': 'list'}, 'Displays a messagebox with list content to the user.', ListMessageboxComponent); +} + +export function provideListNotificationIntentHandler(): NotificationIntentHandler { + return new NotificationIntentHandler({'type': 'list'}, 'Shows a notification with list content to the user.', ListNotificationComponent); +} diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-notify-activity-action/custom-notify-activity-action-provider.service.ts b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-notify-activity-action/custom-notify-activity-action-provider.service.ts new file mode 100644 index 000000000..8800c47e9 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-notify-activity-action/custom-notify-activity-action-provider.service.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Injectable } from '@angular/core'; +import { ActivityActionProvider } from '@scion/workbench-application-platform'; +import { CustomNotifyActivityActionComponent } from './custom-notify-activity-action.component'; +import { CustomActivityActionTypes } from '@scion/e2e/common'; + +@Injectable() +export class CustomNotifyActivityActionProvider implements ActivityActionProvider { + public type = CustomActivityActionTypes.CustomNotify; + public component = CustomNotifyActivityActionComponent; +} + diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-notify-activity-action/custom-notify-activity-action.component.html b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-notify-activity-action/custom-notify-activity-action.component.html new file mode 100644 index 000000000..aeb7d75de --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-notify-activity-action/custom-notify-activity-action.component.html @@ -0,0 +1,7 @@ + + notifications_none + diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-notify-activity-action/custom-notify-activity-action.component.scss b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-notify-activity-action/custom-notify-activity-action.component.scss new file mode 100644 index 000000000..1400644bc --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-notify-activity-action/custom-notify-activity-action.component.scss @@ -0,0 +1,12 @@ +:host { + display: flex; + align-items: center; + justify-content: center; + padding: .5em; + + > a { + text-decoration: none; + color: rgb(239, 239, 239); + outline: none; + } +} diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-notify-activity-action/custom-notify-activity-action.component.ts b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-notify-activity-action/custom-notify-activity-action.component.ts new file mode 100644 index 000000000..b4abc5236 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/custom-notify-activity-action/custom-notify-activity-action.component.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, Inject } from '@angular/core'; +import { NotificationService } from '@scion/workbench'; +import { ACTIVITY_ACTION } from '@scion/workbench-application-platform'; +import { CustomNotifyActivityAction } from '@scion/e2e/common'; + +@Component({ + selector: 'app-custom-notify-activity-action', + templateUrl: './custom-notify-activity-action.component.html', + styleUrls: ['./custom-notify-activity-action.component.scss'] +}) +export class CustomNotifyActivityActionComponent { + + constructor(@Inject(ACTIVITY_ACTION) public action: CustomNotifyActivityAction, + private _notificationService: NotificationService) { + } + + public onClick(event: Event): void { + event.preventDefault(); + this._notificationService.notify({ + content: this.action.properties.text, + cssClass: this.action.properties.cssClass, + }); + } +} diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-messagebox/list-messagebox.component.html b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-messagebox/list-messagebox.component.html new file mode 100644 index 000000000..405ac2b7f --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-messagebox/list-messagebox.component.html @@ -0,0 +1,4 @@ +{{messageBox.input?.text}} +
    +
  • {{item}}
  • +
diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-messagebox/list-messagebox.component.scss b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-messagebox/list-messagebox.component.scss new file mode 100644 index 000000000..965a822dc --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-messagebox/list-messagebox.component.scss @@ -0,0 +1,5 @@ +:host { + > ul { + padding: 0 0 0 1.5em; + } +} diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-messagebox/list-messagebox.component.ts b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-messagebox/list-messagebox.component.ts new file mode 100644 index 000000000..4c7fbc571 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-messagebox/list-messagebox.component.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component } from '@angular/core'; +import { MessageBox } from '@scion/workbench'; + +@Component({ + selector: 'app-list-messagebox', + templateUrl: './list-messagebox.component.html', + styleUrls: ['./list-messagebox.component.scss'], +}) +export class ListMessageboxComponent { + + constructor(public messageBox: MessageBox) { + } +} diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-notification/list-notification.component.html b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-notification/list-notification.component.html new file mode 100644 index 000000000..c72874272 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-notification/list-notification.component.html @@ -0,0 +1,4 @@ +{{notification.input?.text}} +
    +
  • {{item}}
  • +
diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-notification/list-notification.component.scss b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-notification/list-notification.component.scss new file mode 100644 index 000000000..965a822dc --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-notification/list-notification.component.scss @@ -0,0 +1,5 @@ +:host { + > ul { + padding: 0 0 0 1.5em; + } +} diff --git a/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-notification/list-notification.component.ts b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-notification/list-notification.component.ts new file mode 100644 index 000000000..bb1669e22 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/app/custom-extension/list-notification/list-notification.component.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component } from '@angular/core'; +import { Notification } from '@scion/workbench'; + +@Component({ + selector: 'app-list-notification', + templateUrl: './list-notification.component.html', + styleUrls: ['./list-notification.component.scss'] +}) +export class ListNotificationComponent { + + constructor(public notification: Notification) { + } +} diff --git a/projects/e2e/workbench-application-platform/host-app/src/assets/.gitkeep b/projects/e2e/workbench-application-platform/host-app/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/projects/e2e/workbench-application-platform/host-app/src/assets/angular-io-manifest.json b/projects/e2e/workbench-application-platform/host-app/src/assets/angular-io-manifest.json new file mode 100644 index 000000000..2734593ee --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/assets/angular-io-manifest.json @@ -0,0 +1,30 @@ +{ + "name": "Angular IO", + "baseUrl": "https://angular.io/", + "capabilities": [ + { + "type": "view", + "qualifier": { + "resource": "features" + }, + "description": "Shows features & benefits of the Angular project", + "properties": { + "title": "Angular", + "heading": "Features & Benefits", + "path": "/features" + } + }, + { + "type": "view", + "qualifier": { + "resource": "docs" + }, + "description": "Shows the documentation of the Angular project", + "properties": { + "title": "Angular", + "heading": "Documentation", + "path": "/docs" + } + } + ] +} diff --git a/projects/e2e/workbench-application-platform/host-app/src/assets/fonts/wb-icon.eot b/projects/e2e/workbench-application-platform/host-app/src/assets/fonts/wb-icon.eot new file mode 100644 index 000000000..460bbadc1 Binary files /dev/null and b/projects/e2e/workbench-application-platform/host-app/src/assets/fonts/wb-icon.eot differ diff --git a/projects/e2e/workbench-application-platform/host-app/src/assets/fonts/wb-icon.svg b/projects/e2e/workbench-application-platform/host-app/src/assets/fonts/wb-icon.svg new file mode 100644 index 000000000..3a2f8477f --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/assets/fonts/wb-icon.svg @@ -0,0 +1,14 @@ + + + +Generated by IcoMoon + + + + + + + + + + \ No newline at end of file diff --git a/projects/e2e/workbench-application-platform/host-app/src/assets/fonts/wb-icon.ttf b/projects/e2e/workbench-application-platform/host-app/src/assets/fonts/wb-icon.ttf new file mode 100644 index 000000000..042af077e Binary files /dev/null and b/projects/e2e/workbench-application-platform/host-app/src/assets/fonts/wb-icon.ttf differ diff --git a/projects/e2e/workbench-application-platform/host-app/src/assets/fonts/wb-icon.woff b/projects/e2e/workbench-application-platform/host-app/src/assets/fonts/wb-icon.woff new file mode 100644 index 000000000..8e98ad140 Binary files /dev/null and b/projects/e2e/workbench-application-platform/host-app/src/assets/fonts/wb-icon.woff differ diff --git a/projects/e2e/workbench-application-platform/host-app/src/environments/environment.now.ts b/projects/e2e/workbench-application-platform/host-app/src/environments/environment.now.ts new file mode 100644 index 000000000..17a8085bc --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/environments/environment.now.ts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +export const environment = { + production: true, + contact_app_manifest_url: 'https://scion-workbench-application-platform-contact.now.sh/assets/manifest.json', + communication_app_manifest_url: 'https://scion-workbench-application-platform-communication.now.sh/assets/manifest.json', + devtools_app_manifest_url: 'https://scion-workbench-application-platform-devtools.now.sh/assets/manifest.json', + testing_app_manifest_url: '' +}; diff --git a/projects/e2e/workbench-application-platform/host-app/src/environments/environment.ts b/projects/e2e/workbench-application-platform/host-app/src/environments/environment.ts new file mode 100644 index 000000000..84e48f7cf --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/environments/environment.ts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --configuration=now` replaces `environment.ts` with `environment.now.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false, + contact_app_manifest_url: 'http://localhost:5001/assets/manifest.json', + communication_app_manifest_url: 'http://localhost:5002/assets/manifest.json', + devtools_app_manifest_url: 'http://localhost:5003/assets/manifest.json', + testing_app_manifest_url: 'http://localhost:5004/assets/manifest.json', +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/projects/e2e/workbench-application-platform/host-app/src/favicon.ico b/projects/e2e/workbench-application-platform/host-app/src/favicon.ico new file mode 100644 index 000000000..30ac1ce19 Binary files /dev/null and b/projects/e2e/workbench-application-platform/host-app/src/favicon.ico differ diff --git a/projects/e2e/workbench-application-platform/host-app/src/index.html b/projects/e2e/workbench-application-platform/host-app/src/index.html new file mode 100644 index 000000000..0f3dc69af --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/index.html @@ -0,0 +1,13 @@ + + + + + Workbench Application Platform + + + + + + + + diff --git a/projects/e2e/workbench-application-platform/host-app/src/main.ts b/projects/e2e/workbench-application-platform/host-app/src/main.ts new file mode 100644 index 000000000..c7b673cf4 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/main.ts @@ -0,0 +1,12 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/projects/e2e/workbench-application-platform/host-app/src/now.json b/projects/e2e/workbench-application-platform/host-app/src/now.json new file mode 100644 index 000000000..2f95df25c --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/now.json @@ -0,0 +1,5 @@ +{ + "version": 2, + "name": "scion-workbench-application-platform", + "alias": "scion-workbench-application-platform" +} diff --git a/projects/e2e/workbench-application-platform/host-app/src/polyfills.ts b/projects/e2e/workbench-application-platform/host-app/src/polyfills.ts new file mode 100644 index 000000000..1d7b9ab94 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/polyfills.ts @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +import 'core-js/es6/symbol'; +import 'core-js/es6/object'; +import 'core-js/es6/function'; +import 'core-js/es6/parse-int'; +import 'core-js/es6/parse-float'; +import 'core-js/es6/number'; +import 'core-js/es6/math'; +import 'core-js/es6/string'; +import 'core-js/es6/date'; +import 'core-js/es6/array'; +import 'core-js/es6/regexp'; +import 'core-js/es6/map'; +import 'core-js/es6/weak-map'; +import 'core-js/es6/set'; +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + **/ +import 'web-animations-js'; // Run `npm install --save web-animations-js`. +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + +/** + * If the application will be indexed by Google Search, the following is required. + * Googlebot uses a renderer based on Chrome 41. + * https://developers.google.com/search/docs/guides/rendering + **/ +// import 'core-js/es6/array'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following for the Reflect API. */ +// import 'core-js/es6/reflect'; + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + */ + + // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + + /* + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + */ +// (window as any).__Zone_enable_cross_context_check = true; + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/projects/e2e/workbench-application-platform/host-app/src/styles.scss b/projects/e2e/workbench-application-platform/host-app/src/styles.scss new file mode 100644 index 000000000..ff72f92b8 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/src/styles.scss @@ -0,0 +1,5 @@ +@import '../../../../scion/workbench/src/theme/theming'; +@import 'common'; + +@include wb-theme(); +@include app-theme(); diff --git a/projects/e2e/workbench-application-platform/host-app/tsconfig.app.json b/projects/e2e/workbench-application-platform/host-app/tsconfig.app.json new file mode 100644 index 000000000..f06ee9dc5 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/tsconfig.app.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../../out-tsc/host-app", + "types": [] + } +} diff --git a/projects/e2e/workbench-application-platform/host-app/tslint.json b/projects/e2e/workbench-application-platform/host-app/tslint.json new file mode 100644 index 000000000..e3e7b64d8 --- /dev/null +++ b/projects/e2e/workbench-application-platform/host-app/tslint.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ] + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/protractor.conf.js b/projects/e2e/workbench-application-platform/test-runner/protractor.conf.js new file mode 100644 index 000000000..a224f8a3f --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/protractor.conf.js @@ -0,0 +1,35 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +const puppeteer = require('puppeteer'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './src/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome', + chromeOptions: { + args: ['--headless', '--window-size=1920,1080'], + binary: puppeteer.executablePath(), + }, + }, + SELENIUM_PROMISE_MANAGER: false, + directConnect: true, + baseUrl: 'http://localhost:5000/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require('ts-node').register({ + project: require('path').join(__dirname, './tsconfig.e2e.json') + }); + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; diff --git a/projects/e2e/workbench-application-platform/test-runner/src/activity.e2e-spec.ts b/projects/e2e/workbench-application-platform/test-runner/src/activity.e2e-spec.ts new file mode 100644 index 000000000..4554ef31f --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/activity.e2e-spec.ts @@ -0,0 +1,1115 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { HostAppPO } from './page-object/host-app.po'; +import { browser, protractor } from 'protractor'; +import { expectActivityToExistButHidden, expectActivityToShow, expectPopupToNotExist, expectPopupToShow, expectViewToNotExist, expectViewToShow } from './util/testing.util'; +import { TestingActivityPO } from './page-object/testing-activity.po'; +import { Testcase4a3a8984ActivityPO } from './page-object/testcase-4a3a8984-activity-po'; +import { Testcase28f32b51ActivityPO } from './page-object/testcase-28f32b51-activity.po'; +import { Testcase5782ab19ActivityPO } from './page-object/testcase-5782ab19-activity.po'; +import { Testcase56657ad1ViewPO } from './page-object/testcase-56657ad1-view.po'; +import { TestcaseC8e40918ViewPO } from './page-object/testcase-c8e40918-view.po'; +import { TestcaseA686d615ViewPO } from './page-object/testcase-a686d615-view.po'; +import { TestingViewPO } from './page-object/testing-view.po'; +import { TestcaseCc977da9ViewPO } from './page-object/testcase-cc977da9-view.po'; +import { TestcaseB6a8fe23ViewPO } from './page-object/testcase-b6a8fe23-view.po'; +import { Testcase9c5319f7PopupPO } from './page-object/testcase-9c5319f7-popup.po'; +import { Testcase45dc693fPopupPO } from './page-object/testcase-45dc693f-popup.po'; +import { TestcaseF4286ac4PopupPO } from './page-object/testcase-f4286ac4-popup.po'; +import { Testcase159913adPopupPO } from './page-object/testcase-159913ad-popup.po'; +import { Testcase8a468258PopupPO } from './page-object/testcase-8a468258-popup.po'; + +const E2E_TESTING_ACTIVITY_CONTEXT: string[] = ['e2e-testing-app', 'e2e-activity', 'e2e-testing-activity']; + +describe('Activity', () => { + + const hostAppPO = new HostAppPO(); + + beforeEach(async () => { + await browser.get('/'); + }); + + describe('Registration', () => { + + it('should be registered as specified in the manifest [testcase: d11be592-activity]', async () => { + const activityItemPO = await hostAppPO.findActivityItem('e2e-activity-d11be592'); + + await expect(activityItemPO.getTitle()).toEqual('d29b10b862ae'); + await expect(activityItemPO.getText()).toEqual(''); + await expect(activityItemPO.getCssClasses()).toContain('e2e-activity-d11be592'); + await expect(activityItemPO.getCssClasses()).toContain('fab'); + await expect(activityItemPO.getCssClasses()).toContain('fa-android'); + + await activityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-d11be592', componentSelector: 'app-testing-activity'}); + + const testingActivityPO = new TestingActivityPO(['e2e-testing-app', 'e2e-activity', 'e2e-activity-d11be592']); + const activityPanelPO = await hostAppPO.findActivityPanel('e2e-activity-d11be592'); + await expect(testingActivityPO.getUrlParameters()).toEqual({manifestMatrixParam: 'manifestMatrixParamValue'}); + await expect(testingActivityPO.getUrlQueryParameters()).toEqual({manifestQueryParam: 'manifestQueryParamValue'}); + await expect(activityPanelPO.getTitle()).toEqual('d29b10b862ae'); + await expect(activityPanelPO.getCssClasses()).toContain('e2e-activity-d11be592'); + }); + }); + + describe('Interaction', () => { + + it('should allow to change activity properties', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-testing-activity', componentSelector: 'app-testing-activity'}); + + const activityInteractionPO = await testingActivityPO.openActivityInteractionPanel(); + const activityItemPO = await hostAppPO.findActivityItem('e2e-testing-activity'); + const activityPanelPO = await hostAppPO.findActivityPanel('e2e-testing-activity'); + + await activityInteractionPO.enterTitle('e2e:title'); + await expect(activityItemPO.getTitle()).toEqual('e2e:title'); + await expect(activityPanelPO.getTitle()).toEqual('e2e:title'); + + await activityInteractionPO.enterItemText('e2e:item-text'); + await expect(activityItemPO.getText()).toEqual('e2e:item-text'); + + await activityInteractionPO.enterItemCssClass('e2e:item-css-class'); + await expect(activityItemPO.getCssClasses()).toContain('e2e:item-css-class'); + await expect(activityPanelPO.getCssClasses()).not.toContain('e2e:item-css-class'); + + const sizeBefore = await activityPanelPO.getSize(); + await activityInteractionPO.enterDeltaPx(100); + const sizeAfter = await activityPanelPO.getSize(); + await expect(sizeAfter.width).toEqual(sizeBefore.width + 100); + }); + + it('should allow to close the activity', async () => { + const activityItemPO = await hostAppPO.findActivityItem('e2e-testing-activity'); + await activityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-testing-activity', componentSelector: 'app-testing-activity'}); + + // close the activity + await activityItemPO.click(); + await expectActivityToExistButHidden({symbolicAppName: 'testing-app', activityCssClass: 'e2e-testing-activity'}); + }); + + it('should attach previously detached activity component instance when activating it [testcase: 4a3a8984-activity]', async () => { + const activityItemPO = await hostAppPO.findActivityItem('e2e-activity-4a3a8984'); + await activityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-4a3a8984', componentSelector: 'app-activity-4a3a8984'}); + + const activityPO = new Testcase4a3a8984ActivityPO(); + const componentInstanceUuid = await activityPO.getComponentInstanceUuid(); + + // close the activity + await activityItemPO.click(); + await expectActivityToExistButHidden({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-4a3a8984'}); + + // open the activity + await activityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-4a3a8984', componentSelector: 'app-activity-4a3a8984'}); + await expect(activityPO.getComponentInstanceUuid()).toEqual(componentInstanceUuid); + + // switch to other activity + const testingActivityItemPO = await hostAppPO.findActivityItem('e2e-testing-activity'); + await testingActivityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-testing-activity', componentSelector: 'app-testing-activity'}); + + // switch back to the activity + await activityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-4a3a8984', componentSelector: 'app-activity-4a3a8984'}); + await expect(activityPO.getComponentInstanceUuid()).toEqual(componentInstanceUuid); + }); + + it('should receive an activate event if activated, and receive a deactivate event if deactivated [testcase: 28f32b51-activity]', async () => { + const activityItemPO = await hostAppPO.findActivityItem('e2e-activity-28f32b51'); + await activityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-28f32b51', componentSelector: 'app-activity-28f32b51'}); + + const activityPO = new Testcase28f32b51ActivityPO(); + await expect(activityPO.readActiveLog()).toEqual([true]); + + // close the activity + await activityItemPO.click(); + await expectActivityToExistButHidden({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-28f32b51'}); + + // open the activity + await activityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-28f32b51', componentSelector: 'app-activity-28f32b51'}); + await expect(activityPO.readActiveLog()).toEqual([true, false, true]); + + // switch to other activity + const testingActivityItemPO = await hostAppPO.findActivityItem('e2e-testing-activity'); + await testingActivityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-testing-activity', componentSelector: 'app-testing-activity'}); + + // switch back to the activity + await activityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-28f32b51', componentSelector: 'app-activity-28f32b51'}); + await expect(activityPO.readActiveLog()).toEqual([true, false, true, false, true]); + }); + + it('should not show the iframe of inactive activities [testcase: 6d806bea-activity]', async () => { + const activityItemPO = await hostAppPO.findActivityItem('e2e-activity-6d806bea'); + await activityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-6d806bea', componentSelector: 'app-activity-6d806bea'}); + + // close the activity + await activityItemPO.click(); + await expectActivityToExistButHidden({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-6d806bea'}); + + // open the activity + await activityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-6d806bea', componentSelector: 'app-activity-6d806bea'}); + + // switch to other activity + const testingActivityItemPO = await hostAppPO.findActivityItem('e2e-testing-activity'); + await testingActivityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-testing-activity', componentSelector: 'app-testing-activity'}); + await expectActivityToExistButHidden({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-6d806bea'}); + + // switch back to the activity + await activityItemPO.click(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-6d806bea', componentSelector: 'app-activity-6d806bea'}); + await expectActivityToExistButHidden({symbolicAppName: 'testing-app', activityCssClass: 'e2e-testing-activity'}); + }); + }); + + describe('Focus', () => { + + it('should cycle the focus in the activity [testcase: 5782ab19-activity]', async () => { + await hostAppPO.clickActivityItem('e2e-activity-5782ab19'); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-5782ab19', componentSelector: 'app-activity-5782ab19'}); + + const activityPO = new Testcase5782ab19ActivityPO(); + await expect(activityPO.isActiveElement('field-1')).toBeTruthy('(1)'); + + await activityPO.pressTab(); + await expect(activityPO.isActiveElement('field-2')).toBeTruthy('(2)'); + + await activityPO.pressTab(); + await expect(activityPO.isActiveElement('field-3')).toBeTruthy('(3)'); + + await activityPO.pressTab(); + await expect(activityPO.isActiveElement('field-1')).toBeTruthy('(4)'); + + await activityPO.pressTab(); + await expect(activityPO.isActiveElement('field-2')).toBeTruthy('(5)'); + + await activityPO.pressTab(); + await expect(activityPO.isActiveElement('field-3')).toBeTruthy('(6)'); + + await activityPO.pressShiftTab(); + await expect(activityPO.isActiveElement('field-2')).toBeTruthy('(7)'); + + await activityPO.pressShiftTab(); + await expect(activityPO.isActiveElement('field-1')).toBeTruthy('(8)'); + + await activityPO.pressShiftTab(); + await expect(activityPO.isActiveElement('field-3')).toBeTruthy('(9)'); + + await activityPO.pressShiftTab(); + await expect(activityPO.isActiveElement('field-2')).toBeTruthy('(10)'); + + await activityPO.pressShiftTab(); + await expect(activityPO.isActiveElement('field-1')).toBeTruthy('(11)'); + }); + + it('should restore the focus upon activity activation [testcase: 5782ab19-activity]', async () => { + await hostAppPO.clickActivityItem('e2e-activity-5782ab19'); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-5782ab19', componentSelector: 'app-activity-5782ab19'}); + + const activityPO = new Testcase5782ab19ActivityPO(); + await expect(activityPO.isActiveElement('field-1')).toBeTruthy('(1)'); + + await activityPO.pressTab(); + await expect(activityPO.isActiveElement('field-2')).toBeTruthy('(2)'); + + await hostAppPO.clickActivityItem('e2e-testing-activity'); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-testing-activity', componentSelector: 'app-testing-activity'}); + + await hostAppPO.clickActivityItem('e2e-activity-5782ab19'); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-5782ab19', componentSelector: 'app-activity-5782ab19'}); + await expect(activityPO.isActiveElement('field-2')).toBeTruthy('(3)'); + }); + + it('should autofocus the first element [testcase: 5782ab19-activity]', async () => { + await hostAppPO.clickActivityItem('e2e-activity-5782ab19'); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-activity-5782ab19', componentSelector: 'app-activity-5782ab19'}); + + const activityPO = new Testcase5782ab19ActivityPO(); + await expect(activityPO.isActiveElement('field-1')).toBeTruthy('Expected first field to be the active element'); + }); + }); + + describe('Activity View Item', () => { + + it('should allow navigation to private views of the same application (implicit intent) [testcase: 354aa6da-activity]', async () => { + await hostAppPO.clickActivityItem('e2e-activity-354aa6da'); + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-354aa6da', componentSelector: 'app-view-354aa6da'}); + }); + + it('should allow navigation to public views of the same application (implicit intent) [testcase: 85dde646-activity]', async () => { + await hostAppPO.clickActivityItem('e2e-activity-85dde646'); + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-85dde646', componentSelector: 'app-view-85dde646'}); + }); + }); + + describe('Activity Action', () => { + + it('should allow to add and remove activity actions [testcase: 354aa6da-activity]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-testing-activity', componentSelector: 'app-testing-activity'}); + const actionPanelPO = await testingActivityPO.openActivityActionsPanel(); + + // hide action + const action = await actionPanelPO.findCustomActivityAction(); + await actionPanelPO.checkCustomActivityAction(false); + await expect(action.isPresent()).toBeFalsy('(1)'); + + // show action + await actionPanelPO.checkCustomActivityAction(true); + await expect(action.isPresent()).toBeTruthy('(2)'); + + // hide action + await actionPanelPO.checkCustomActivityAction(false); + await expect(action.isPresent()).toBeFalsy('(3)'); + }); + + it('should allow to contribute custom activity actions', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + await expectActivityToShow({symbolicAppName: 'testing-app', activityCssClass: 'e2e-testing-activity', componentSelector: 'app-testing-activity'}); + const actionPanelPO = await testingActivityPO.openActivityActionsPanel(); + await actionPanelPO.checkCustomActivityAction(true); + + + const customAction = await hostAppPO.findActivityAction('e2e-custom-activity-action'); + await customAction.click(); + + await expect(await hostAppPO.findNotification('e2e-custom-activity-action')).toBeTruthy(); + }); + + describe('ViewOpenActivityAction', () => { + + it('should allow navigation to private views of the same application (implicit intent) [testcase: 354aa6da-view]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openViewOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-354aa6da-view-activity-action'); + await panelPO.enterTitle('testcase: 354aa6da-view'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: '354aa6da-view', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-354aa6da-view-activity-action'); + await action.click(); + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-354aa6da', componentSelector: 'app-view-354aa6da'}); + }); + + it('should allow navigation to public views of the same application (implicit intent) [testcase: 85dde646-view]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openViewOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-85dde646-view-activity-action'); + await panelPO.enterTitle('testcase: 85dde646-view'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: '85dde646-view', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-85dde646-view-activity-action'); + await action.click(); + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-85dde646', componentSelector: 'app-view-85dde646'}); + }); + + it('should allow navigation to public views of other applications [testcase: d7da51f14c25]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openViewOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-d7da51f14c25-activity-action'); + await panelPO.enterTitle('testcase: d7da51f14c25'); + await panelPO.enterQualifier({ + entity: 'communication', + presentation: 'list', + contactId: '5', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-d7da51f14c25-activity-action'); + await action.click(); + await expectViewToShow({symbolicAppName: 'communication-app', viewCssClass: 'e2e-communication-list', componentSelector: 'app-communication-view'}); + }); + + it('should not allow navigation to public views of other applications if missing the intent [testcase: 0901198a704b]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openViewOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-0901198a704b-activity-action'); + await panelPO.enterTitle('testcase: 0901198a704b'); + await panelPO.enterQualifier({ + entity: 'communication', + presentation: 'list', + contactId: '999', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-0901198a704b-activity-action'); + await action.click(); + + const notificationPO = await hostAppPO.findNotification('e2e-not-qualified'); + await expect(notificationPO).not.toBeNull(); + await expect(notificationPO.getSeverity()).toEqual('error'); + await expectViewToNotExist({symbolicAppName: 'communication-app', viewCssClass: 'e2e-communication-list'}); + }); + + it('should not allow navigation to private views of other application [testcase: 0427da7f4a49]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openViewOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-0427da7f4a49-activity-action'); + await panelPO.enterTitle('testcase: 0427da7f4a49'); + await panelPO.enterQualifier({ + entity: 'contact', + id: '5', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-0427da7f4a49-activity-action'); + await action.click(); + + const notificationPO = await hostAppPO.findNotification('e2e-not-handled'); + await expect(notificationPO).not.toBeNull(); + await expect(notificationPO.getSeverity()).toEqual('error'); + await expectViewToNotExist({symbolicAppName: 'contact-app', viewCssClass: 'e2e-contact'}); + }); + + it('should allow to provide query and matrix parameters [testcase: 56657ad1-view]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openViewOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-56657ad1-view-activity-action'); + await panelPO.enterTitle('testcase: 56657ad1-view'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: '56657ad1-view', + }); + await panelPO.enterMatrixParams({ + mp1: '41ecdefec0a3', + mp2: 'da67bd554b36', + }); + await panelPO.enterQueryParams({ + qp1: '6378a62700d3', + qp2: 'c5a37d3660ba', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-56657ad1-view-activity-action'); + await action.click(); + + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-56657ad1', componentSelector: 'app-view-56657ad1'}); + + const viewPO = new Testcase56657ad1ViewPO(); + const urlParams = await viewPO.getUrlParameters(); + await expect(urlParams).toEqual({ + mp1: '41ecdefec0a3', + mp2: 'da67bd554b36', + }); + + const urlQueryParams = await viewPO.getUrlQueryParameters(); + await expect(urlQueryParams).toEqual({ + qp1: '6378a62700d3', + qp2: 'c5a37d3660ba', + }); + }); + + it('should allow to receive query and matrix parameters as specified in the manifest [testcase: c8e40918-view]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openViewOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-c8e40918-view-activity-action'); + await panelPO.enterTitle('testcase: c8e40918-view'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: 'c8e40918-view', + }); + + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-c8e40918-view-activity-action'); + await action.click(); + + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-c8e40918', componentSelector: 'app-view-c8e40918'}); + + const viewPO = new TestcaseC8e40918ViewPO(); + const urlParams = await viewPO.getUrlParameters(); + await expect(urlParams).toEqual({ + mp1: 'd52f5f88be27', + mp2: '01aa011fb2f4', + }); + + const urlQueryParams = await viewPO.getUrlQueryParameters(); + await expect(urlQueryParams).toEqual({ + qp1: 'f3227d5926c0', + qp2: '88a9cd0cb937', + }); + }); + + it('should allow to merge query and matrix parameters provided from intent (overwrite) and manifest [testcase: a686d615-view]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openViewOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-a686d615-view-activity-action'); + await panelPO.enterTitle('testcase: a686d615-view'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: 'a686d615-view', + }); + await panelPO.enterMatrixParams({ + mp1: '557c7323a13c', + mp2: '67dbebc8dd7c', + }); + await panelPO.enterQueryParams({ + qp1: 'a3fa547519cf', + qp2: 'db03e5494054', + }); + + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-a686d615-view-activity-action'); + await action.click(); + + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-a686d615', componentSelector: 'app-view-a686d615'}); + + const viewPO = new TestcaseA686d615ViewPO(); + const urlParams = await viewPO.getUrlParameters(); + await expect(urlParams).toEqual({ + mp1: '557c7323a13c', + mp2: '67dbebc8dd7c', + mpx: 'f406422c77fc', + }); + + const urlQueryParams = await viewPO.getUrlQueryParameters(); + await expect(urlQueryParams).toEqual({ + qp1: 'a3fa547519cf', + qp2: 'db03e5494054', + qpx: '18c9d6c51b0c', + }); + }); + + it('should allow to navigate to an opened view (activateIfPresent=true) [testcase: 4a4e6970-view]', async () => { + // Open testing view to open '4a4e6970-view' + const testingViewPO = new TestingViewPO(); + await testingViewPO.navigateTo(); + const viewNavigationPO = await testingViewPO.openViewNavigationPanel(); + await viewNavigationPO.enterQualifier({ + entity: 'testing', + testcase: '4a4e6970-view', + }); + await viewNavigationPO.selectTarget('blank'); + await viewNavigationPO.execute(); + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-4a4e6970', componentSelector: 'app-view-4a4e6970'}); + + // Go back to the testing view + await hostAppPO.clickViewTab('e2e-testing-view'); + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-testing-view', componentSelector: 'app-testing-view'}); + await expect(hostAppPO.getViewTabCount()).toBe(2); + + // Add activity action to activate the testing view + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openViewOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-4a4e6970-view-activity-action'); + await panelPO.enterTitle('testcase: 4a4e6970-view'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: '4a4e6970-view', + }); + await panelPO.checkActivateIfPresent(true); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-4a4e6970-view-activity-action'); + await action.click(); + + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-4a4e6970', componentSelector: 'app-view-4a4e6970'}); + await expect(hostAppPO.getViewTabCount()).toBe(2); + }); + + + it('should allow to close an opened view (closeIfPresent=true) [testcase: 608aa47c-view]', async () => { + // Open testing view to open '4a4e6970-view' + const testingViewPO = new TestingViewPO(); + await testingViewPO.navigateTo(); + const viewNavigationPO = await testingViewPO.openViewNavigationPanel(); + await viewNavigationPO.enterQualifier({ + entity: 'testing', + testcase: '608aa47c-view', + }); + await viewNavigationPO.selectTarget('blank'); + await viewNavigationPO.execute(); + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-608aa47c', componentSelector: 'app-view-608aa47c'}); + + // Add activity action to close the testing view + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openViewOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-608aa47c-view-activity-action'); + await panelPO.enterTitle('testcase: 608aa47c-view'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: '608aa47c-view', + }); + await panelPO.checkCloseIfPresent(true); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-608aa47c-view-activity-action'); + await action.click(); + + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-testing-view', componentSelector: 'app-testing-view'}); + await expect(hostAppPO.getViewTabCount()).toBe(1); + }); + + + it('should substitute path parameters with values from the intent qualifier [testcase: cc977da9-view]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openViewOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-cc977da9-view-activity-action'); + await panelPO.enterTitle('testcase: cc977da9-view'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: 'cc977da9-view', + qualifierParam1: 'e82bf49c4768', + qualifierParam2: '1b84a4a926f7' + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-cc977da9-view-activity-action'); + await action.click(); + + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-cc977da9', componentSelector: 'app-view-cc977da9'}); + + const viewPO = new TestcaseCc977da9ViewPO(); + const urlParams = await viewPO.getUrlParameters(); + await expect(urlParams).toEqual({ + a: 'e82bf49c4768', + b: '1b84a4a926f7', + }); + + const urlQueryParams = await viewPO.getUrlQueryParameters(); + await expect(urlQueryParams).toBeNull(); + }); + + it('should substitute matrix and query parameters with values from the intent qualifier [testcase: b6a8fe23-view]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openViewOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-b6a8fe23-view-activity-action'); + await panelPO.enterTitle('testcase: b6a8fe23-view'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: 'b6a8fe23-view', + qualifierParam1: 'd8b74df2c77d', + qualifierParam2: 'e60c81360bee', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-b6a8fe23-view-activity-action'); + await action.click(); + + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-b6a8fe23', componentSelector: 'app-view-b6a8fe23'}); + + const viewPO = new TestcaseB6a8fe23ViewPO(); + const urlParams = await viewPO.getUrlParameters(); + await expect(urlParams).toEqual({ + matrixParam1: 'd8b74df2c77d', + matrixParam2: 'eda2e91468e1', + pathParam1: 'd8b74df2c77d', + }); + + const urlQueryParams = await viewPO.getUrlQueryParameters(); + await expect(urlQueryParams).toEqual({ + queryParam1: 'e60c81360bee', + queryParam2: '1a3d3aaf937e' + }); + }); + }); + + describe('PopupOpenActivityAction', () => { + + it('should allow navigation to private popups of the same application (implicit intent) [testcase: 1a90c8d2-popup]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-1a90c8d2-popup-activity-action'); + await panelPO.enterTitle('testcase: 1a90c8d2-popup'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: '1a90c8d2-popup', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-1a90c8d2-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-1a90c8d2', componentSelector: 'app-popup-1a90c8d2'}); + }); + + it('should allow navigation to public popups of the same application (implicit intent) [testcase: 7330f506-popup]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-7330f506-popup-activity-action'); + await panelPO.enterTitle('testcase: 7330f506-popup'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: '7330f506-popup', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-7330f506-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-7330f506', componentSelector: 'app-popup-7330f506'}); + }); + + it('should allow navigation to public popups of other applications [testcase: 924e114f777c]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-924e114f777c-popup-activity-action'); + await panelPO.enterTitle('testcase: 924e114f777c-popup'); + await panelPO.enterQualifier({ + entity: 'communication', + action: 'create', + contactId: '5', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-924e114f777c-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'communication-app', popupCssClass: 'e2e-communication-create', componentSelector: 'app-communication-new-popup'}); + }); + + it('should not allow navigation to public popups of other applications if missing the intent [testcase: 13370b2aa222]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-13370b2aa222-popup-activity-action'); + await panelPO.enterTitle('testcase: 13370b2aa222-popup'); + await panelPO.enterQualifier({ + entity: 'communication', + action: 'create', + contactId: '999', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-13370b2aa222-popup-activity-action'); + await action.click(); + + const notificationPO = await hostAppPO.findNotification('e2e-not-qualified'); + await expect(notificationPO).not.toBeNull(); + await expect(notificationPO.getSeverity()).toEqual('error'); + await expectPopupToNotExist({symbolicAppName: 'communication-app', popupCssClass: 'e2e-communication-create'}); + }); + + it('should not allow navigation to private popups of other application [testcase: cf12d60debc8]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-cf12d60debc8-popup-activity-action'); + await panelPO.enterTitle('testcase: cf12d60debc8-popup'); + await panelPO.enterQualifier({ + entity: 'contact', + action: 'create', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-cf12d60debc8-popup-activity-action'); + await action.click(); + + const notificationPO = await hostAppPO.findNotification('e2e-not-handled'); + await expect(notificationPO).not.toBeNull(); + await expect(notificationPO.getSeverity()).toEqual('error'); + await expectPopupToNotExist({symbolicAppName: 'contact-app', popupCssClass: 'e2e-contact'}); + }); + + it('should allow to provide query and matrix parameters [testcase: 9c5319f7-popup]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-9c5319f7-popup-activity-action'); + await panelPO.enterTitle('testcase: 9c5319f7-popup'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: '9c5319f7-popup' + }); + await panelPO.enterMatrixParams({ + mp1: '41ecdefec0a3', + mp2: 'da67bd554b36', + }); + await panelPO.enterQueryParams({ + qp1: '6378a62700d3', + qp2: 'c5a37d3660ba', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-9c5319f7-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-9c5319f7', componentSelector: 'app-popup-9c5319f7'}); + + const popupPO = new Testcase9c5319f7PopupPO(); + const urlParams = await popupPO.getUrlParameters(); + await expect(urlParams).toEqual({ + mp1: '41ecdefec0a3', + mp2: 'da67bd554b36', + }); + + const urlQueryParams = await popupPO.getUrlQueryParameters(); + await expect(urlQueryParams).toEqual({ + qp1: '6378a62700d3', + qp2: 'c5a37d3660ba', + }); + }); + + it('should allow to receive query and matrix parameters as specified in the manifest [testcase: 45dc693f-popup]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-45dc693f-popup-activity-action'); + await panelPO.enterTitle('testcase: 45dc693f-popup'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: '45dc693f-popup', + }); + + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-45dc693f-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-45dc693f', componentSelector: 'app-popup-45dc693f'}); + + const popupPO = new Testcase45dc693fPopupPO(); + const urlParams = await popupPO.getUrlParameters(); + await expect(urlParams).toEqual({ + mp1: 'd52f5f88be27', + mp2: '01aa011fb2f4', + }); + + const urlQueryParams = await popupPO.getUrlQueryParameters(); + await expect(urlQueryParams).toEqual({ + qp1: 'f3227d5926c0', + qp2: '88a9cd0cb937', + }); + }); + + it('should allow to merge query and matrix parameters provided from intent (overwrite) and manifest [testcase: f4286ac4-popup]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-f4286ac4-popup-activity-action'); + await panelPO.enterTitle('testcase: f4286ac4-popup'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: 'f4286ac4-popup', + }); + await panelPO.enterMatrixParams({ + mp1: '557c7323a13c', + mp2: '67dbebc8dd7c', + }); + await panelPO.enterQueryParams({ + qp1: 'a3fa547519cf', + qp2: 'db03e5494054', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-f4286ac4-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-f4286ac4', componentSelector: 'app-popup-f4286ac4'}); + + const popupPO = new TestcaseF4286ac4PopupPO(); + const urlParams = await popupPO.getUrlParameters(); + await expect(urlParams).toEqual({ + mp1: '557c7323a13c', + mp2: '67dbebc8dd7c', + mpx: 'f406422c77fc', + }); + + const urlQueryParams = await popupPO.getUrlQueryParameters(); + await expect(urlQueryParams).toEqual({ + qp1: 'a3fa547519cf', + qp2: 'db03e5494054', + qpx: '18c9d6c51b0c', + }); + }); + + it('should substitute path parameters with values from the intent qualifier [testcase: 159913ad-popup]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-159913ad-popup-activity-action'); + await panelPO.enterTitle('testcase: 159913ad-popup'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: '159913ad-popup', + qualifierParam1: 'e82bf49c4768', + qualifierParam2: '1b84a4a926f7' + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-159913ad-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-159913ad', componentSelector: 'app-popup-159913ad'}); + + const popupPO = new Testcase159913adPopupPO(); + const urlParams = await popupPO.getUrlParameters(); + await expect(urlParams).toEqual({ + a: 'e82bf49c4768', + b: '1b84a4a926f7', + }); + + const urlQueryParams = await popupPO.getUrlQueryParameters(); + await expect(urlQueryParams).toBeNull(); + }); + + it('should substitute matrix and query parameters with values from the intent qualifier [testcase: 8a468258-popup]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-8a468258-popup-activity-action'); + await panelPO.enterTitle('testcase: 8a468258-popup'); + await panelPO.enterQualifier({ + entity: 'testing', + testcase: '8a468258-popup', + qualifierParam1: 'd8b74df2c77d', + qualifierParam2: 'e60c81360bee', + }); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-8a468258-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-8a468258', componentSelector: 'app-popup-8a468258'}); + + const popupPO = new Testcase8a468258PopupPO(); + const urlParams = await popupPO.getUrlParameters(); + await expect(urlParams).toEqual({ + matrixParam1: 'd8b74df2c77d', + matrixParam2: 'eda2e91468e1', + pathParam1: 'd8b74df2c77d', + }); + + const urlQueryParams = await popupPO.getUrlQueryParameters(); + await expect(urlQueryParams).toEqual({ + queryParam1: 'e60c81360bee', + queryParam2: '1a3d3aaf937e' + }); + }); + + + it('should close on focus lost [testcase: 9002887a395c]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-9002887a395c-popup-activity-action'); + await panelPO.enterTitle('testcase: 9002887a395c-popup'); + await panelPO.enterQualifier({ + entity: 'testing', + }); + await panelPO.checkCloseOnFocusLost(true); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-9002887a395c-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + + // remove focus from the popup + await testingActivityPO.closePopupOpenActivityActionPanel(); + await expectPopupToNotExist({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup'}); + }); + + it('should not close on focus lost [testcase: 2e220909241f]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-2e220909241f-popup-activity-action'); + await panelPO.enterTitle('testcase: 2e220909241f-popup'); + await panelPO.enterQualifier({ + entity: 'testing', + }); + await panelPO.checkCloseOnFocusLost(false); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-2e220909241f-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + + // remove focus from the popup + await testingActivityPO.closePopupOpenActivityActionPanel(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + }); + + it('should close on escape keystroke [testcase: e2a384f5294a]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-e2a384f5294a-popup-activity-action'); + await panelPO.enterTitle('testcase: e2a384f5294a-popup'); + await panelPO.enterQualifier({ + entity: 'testing', + }); + await panelPO.checkCloseOnEscape(true); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-e2a384f5294a-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + + // send escape keystroke + await browser.actions().sendKeys(protractor.Key.ESCAPE).perform(); + await expectPopupToNotExist({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup'}); + }); + + it('should not close on escape keystroke [testcase: 5aaa5df79806]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-5aaa5df79806-popup-activity-action'); + await panelPO.enterTitle('testcase: 5aaa5df79806-popup'); + await panelPO.enterQualifier({ + entity: 'testing', + }); + await panelPO.checkCloseOnEscape(false); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-5aaa5df79806-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + + // send escape keystroke + await browser.actions().sendKeys(protractor.Key.ESCAPE).perform(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + }); + + it('should close on grid layout change [testcase: c0ad45e6c742]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-c0ad45e6c742-popup-activity-action'); + await panelPO.enterTitle('testcase: c0ad45e6c742-popup'); + await panelPO.enterQualifier({ + entity: 'testing', + }); + await panelPO.checkCloseOnFocusLost(false); + await panelPO.checkCloseOnGridLayoutChange(true); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-c0ad45e6c742-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + + // Make a grid layout change + await hostAppPO.moveActivitySash(100); + await expectPopupToNotExist({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup'}); + }); + + it('should not close on grid layout change [testcase: a6919dfd2fb1]', async () => { + const testingActivityPO = new TestingActivityPO(E2E_TESTING_ACTIVITY_CONTEXT); + await testingActivityPO.navigateTo(); + + const panelPO = await testingActivityPO.openPopupOpenActivityActionPanel(); + await panelPO.enterCssClass('fab fa-android e2e-a6919dfd2fb1-popup-activity-action'); + await panelPO.enterTitle('testcase: a6919dfd2fb1-popup'); + await panelPO.enterQualifier({ + entity: 'testing', + }); + await panelPO.checkCloseOnFocusLost(false); + await panelPO.checkCloseOnGridLayoutChange(false); + await panelPO.addAction(); + + // Execute the action + const action = await hostAppPO.findActivityAction('e2e-a6919dfd2fb1-popup-activity-action'); + await action.click(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + + // Make a grid layout change + await hostAppPO.moveActivitySash(100); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + }); + }); + }); +}); diff --git a/projects/e2e/workbench-application-platform/test-runner/src/message-box.e2e-spec.ts b/projects/e2e/workbench-application-platform/test-runner/src/message-box.e2e-spec.ts new file mode 100644 index 000000000..d04d903f9 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/message-box.e2e-spec.ts @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { TestingViewPO } from './page-object/testing-view.po'; +import { HostAppPO } from './page-object/host-app.po'; +import { browser } from 'protractor'; +import { expectViewToNotExist, expectViewToShow } from './util/testing.util'; +import { noop } from 'rxjs'; +import { Testcase61097badMessageBoxPO } from './page-object/testcase-61097bad-msgbox.po'; + +describe('MessageBox', () => { + + const hostAppPO = new HostAppPO(); + const testingViewPO = new TestingViewPO(); + + beforeEach(async () => { + await browser.get('/'); + }); + + it('should show specified actions', async () => { + await testingViewPO.navigateTo(); + const msgboxPanelPO = await testingViewPO.openMessageBoxPanel(); + + await msgboxPanelPO.enterCssClass('e2e-a6cecbaa-msgbox'); + const actions = {ok: 'OK', cancel: 'Cancel', ['127f38d1a966']: '9ce7b0679386'}; + await msgboxPanelPO.enterActions(actions); + + // close via ok action + await msgboxPanelPO.open(); + { + const msgboxPO = await hostAppPO.findMessageBox('e2e-a6cecbaa-msgbox'); + await expect(msgboxPO.getActions()).toEqual(actions); + await msgboxPO.close('ok'); + await expect(msgboxPanelPO.getCloseAction()).toEqual('ok'); + } + + // close via cancel action + await msgboxPanelPO.open(); + { + const msgboxPO = await hostAppPO.findMessageBox('e2e-a6cecbaa-msgbox'); + await expect(msgboxPO.getActions()).toEqual(actions); + await msgboxPO.close('cancel'); + await expect(msgboxPanelPO.getCloseAction()).toEqual('cancel'); + } + + // close via 127f38d1a966 action + await msgboxPanelPO.open(); + { + const msgboxPO = await hostAppPO.findMessageBox('e2e-a6cecbaa-msgbox'); + await expect(msgboxPO.getActions()).toEqual(actions); + await msgboxPO.close('127f38d1a966'); + await expect(msgboxPanelPO.getCloseAction()).toEqual('127f38d1a966'); + } + }); + + it('should show specified text and title', async () => { + await testingViewPO.navigateTo(); + const msgboxPanelPO = await testingViewPO.openMessageBoxPanel(); + + await msgboxPanelPO.enterCssClass('e2e-c5dc3af5cd92-msgbox'); + await msgboxPanelPO.enterText('01de40e40df3'); + await msgboxPanelPO.enterTitle('976653342eef'); + await msgboxPanelPO.enterActions({ok: 'OK'}); + + await msgboxPanelPO.open(); + const msgboxPO = await hostAppPO.findMessageBox('e2e-c5dc3af5cd92-msgbox'); + await expect(msgboxPO.getText()).toEqual('01de40e40df3'); + await expect(msgboxPO.getTitle()).toEqual('976653342eef'); + }); + + it('should show specified severity', async () => { + await testingViewPO.navigateTo(); + const msgboxPanelPO = await testingViewPO.openMessageBoxPanel(); + + await msgboxPanelPO.enterCssClass('e2e-fbf7ec24f2d4-msgbox'); + await msgboxPanelPO.enterActions({ok: 'OK'}); + + // info + await msgboxPanelPO.selectSeverity('info'); + await msgboxPanelPO.open(); + { + const msgboxPO = await hostAppPO.findMessageBox('e2e-fbf7ec24f2d4-msgbox'); + await expect(msgboxPO.getSeverity()).toEqual('info'); + await msgboxPO.close('ok'); + } + + // warn + await msgboxPanelPO.selectSeverity('warn'); + await msgboxPanelPO.open(); + { + const msgboxPO = await hostAppPO.findMessageBox('e2e-fbf7ec24f2d4-msgbox'); + await expect(msgboxPO.getSeverity()).toEqual('warn'); + await msgboxPO.close('ok'); + } + + // error + await msgboxPanelPO.selectSeverity('error'); + await msgboxPanelPO.open(); + { + const msgboxPO = await hostAppPO.findMessageBox('e2e-fbf7ec24f2d4-msgbox'); + await expect(msgboxPO.getSeverity()).toEqual('error'); + await msgboxPO.close('ok'); + } + }); + + it('should apply view modality [testcase: c91805e8]', async () => { + await testingViewPO.navigateTo(); + const msgboxPanelPO = await testingViewPO.openMessageBoxPanel(); + + await msgboxPanelPO.enterCssClass('e2e-dfa87d85eb1f-msgbox'); + await msgboxPanelPO.selectModality('view'); + await msgboxPanelPO.enterActions({ok: 'OK'}); + await msgboxPanelPO.open(); + { + const msgboxPO = await hostAppPO.findMessageBox('e2e-dfa87d85eb1f-msgbox'); + await expect(msgboxPO.isDisplayed()).toBeTruthy('expected messagebox to be displayed'); + } + + // test if other view can be opened via activity bar + { + await hostAppPO.clickActivityItem('e2e-activity-c91805e8'); + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-c91805e8', componentSelector: 'app-view-c91805e8'}); + const msgboxPO = await hostAppPO.findMessageBox('e2e-dfa87d85eb1f-msgbox'); + await expect(msgboxPO.isDisplayed()).toBeFalsy('expected messagebox not to be displayed'); + } + + // switch to disabled view + { + await hostAppPO.clickViewTab('e2e-testing-view'); + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-testing-view', componentSelector: 'app-testing-view'}); + const msgboxPO = await hostAppPO.findMessageBox('e2e-dfa87d85eb1f-msgbox'); + await expect(msgboxPO.isDisplayed()).toBeTruthy('expected messagebox to be displayed'); + } + }); + + it('should apply application modality [testcase: c91805e8]', async () => { + await testingViewPO.navigateTo(); + const msgboxPanelPO = await testingViewPO.openMessageBoxPanel(); + + await msgboxPanelPO.enterCssClass('e2e-dfa87d85eb1f-msgbox'); + await msgboxPanelPO.selectModality('application'); + await msgboxPanelPO.enterActions({ok: 'OK'}); + await msgboxPanelPO.open(); + { + const msgboxPO = await hostAppPO.findMessageBox('e2e-dfa87d85eb1f-msgbox'); + await expect(msgboxPO.isDisplayed()).toBeTruthy('expected messagebox to be displayed'); + } + + // test that other view cannot be opened via activity bar + { + await hostAppPO.clickActivityItem('e2e-activity-c91805e8').then(() => fail('expected activity item not to be clickable')).catch(noop); + await expectViewToNotExist({symbolicAppName: 'testing-app', viewCssClass: 'e2e-view-c91805e8'}); + await expectViewToShow({symbolicAppName: 'testing-app', viewCssClass: 'e2e-testing-view', componentSelector: 'app-testing-view'}); + const msgboxPO = await hostAppPO.findMessageBox('e2e-dfa87d85eb1f-msgbox'); + await expect(msgboxPO.isDisplayed()).toBeTruthy('expected messagebox to be displayed'); + } + }); + + it('should make text selectable', async () => { + await testingViewPO.navigateTo(); + const msgboxPanelPO = await testingViewPO.openMessageBoxPanel(); + + await msgboxPanelPO.enterCssClass('e2e-cc1bea0bdaa0-msgbox'); + await msgboxPanelPO.enterText('some selectable text'); + await msgboxPanelPO.checkContentSelectable(true); + await msgboxPanelPO.enterActions({ok: 'OK'}); + + await msgboxPanelPO.open(); + const msgboxPO = await hostAppPO.findMessageBox('e2e-cc1bea0bdaa0-msgbox'); + await expect(msgboxPO.isContentSelectable()).toBeTruthy('expected be seletable'); + }); + + it('should make text not selectable', async () => { + await testingViewPO.navigateTo(); + const msgboxPanelPO = await testingViewPO.openMessageBoxPanel(); + + await msgboxPanelPO.enterCssClass('e2e-cc1bea0bdaa0-msgbox'); + await msgboxPanelPO.enterText('some selectable text'); + await msgboxPanelPO.checkContentSelectable(false); + await msgboxPanelPO.enterActions({ok: 'OK'}); + + await msgboxPanelPO.open(); + const msgboxPO = await hostAppPO.findMessageBox('e2e-cc1bea0bdaa0-msgbox'); + await expect(msgboxPO.isContentSelectable()).toBeFalsy('expected not be seletable'); + }); + + it('should show a custom message box [testcase: 61097bad]', async () => { + await testingViewPO.navigateTo(); + const msgboxPanelPO = await testingViewPO.openMessageBoxPanel(); + + await msgboxPanelPO.enterQualifier({'type': 'list'}); + await msgboxPanelPO.enterCssClass('e2e-61097bad-msgbox'); + await msgboxPanelPO.enterPayload({'items': ['a', 'b', 'c']}); + await msgboxPanelPO.enterActions({ok: 'OK'}); + + await msgboxPanelPO.open(); + const msgboxPO = new Testcase61097badMessageBoxPO('e2e-61097bad-msgbox'); + await expect(msgboxPO.getItems()).toEqual(['a', 'b', 'c']); + }); +}); diff --git a/projects/e2e/workbench-application-platform/test-runner/src/notifcation-box.e2e-spec.ts b/projects/e2e/workbench-application-platform/test-runner/src/notifcation-box.e2e-spec.ts new file mode 100644 index 000000000..4c4c4b6cf --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/notifcation-box.e2e-spec.ts @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { TestingViewPO } from './page-object/testing-view.po'; +import { HostAppPO } from './page-object/host-app.po'; +import { browser } from 'protractor'; +import { Testcase61097badNotificationPO } from './page-object/testcase-61097bad-notification.po'; + +describe('Notification', () => { + + const hostAppPO = new HostAppPO(); + const testingViewPO = new TestingViewPO(); + + beforeEach(async () => { + await browser.get('/'); + }); + + it('should show specified text and title', async () => { + await testingViewPO.navigateTo(); + const notificationPanelPO = await testingViewPO.openNotificationPanel(); + await notificationPanelPO.enterCssClass('e2e-c5dc3af5cd92-notification'); + await notificationPanelPO.enterText('01de40e40df3'); + await notificationPanelPO.enterTitle('976653342eef'); + await notificationPanelPO.show(); + + const notificationPO = await hostAppPO.findNotification('e2e-c5dc3af5cd92-notification'); + + await expect(notificationPO.getText()).toEqual('01de40e40df3'); + await expect(notificationPO.getTitle()).toEqual('976653342eef'); + }); + + it('should show specified severity', async () => { + await testingViewPO.navigateTo(); + const notificationPanelPO = await testingViewPO.openNotificationPanel(); + await notificationPanelPO.enterCssClass('e2e-fbf7ec24f2d4-notification'); + + // info + await notificationPanelPO.selectSeverity('info'); + await notificationPanelPO.show(); + { + const notificationPO = await hostAppPO.findNotification('e2e-fbf7ec24f2d4-notification'); + await expect(notificationPO.getSeverity()).toEqual('info'); + await notificationPO.close(); + } + + // warn + await notificationPanelPO.selectSeverity('warn'); + await notificationPanelPO.show(); + { + const notificationPO = await hostAppPO.findNotification('e2e-fbf7ec24f2d4-notification'); + await expect(notificationPO.getSeverity()).toEqual('warn'); + await notificationPO.close(); + } + + // error + await notificationPanelPO.selectSeverity('error'); + await notificationPanelPO.show(); + { + const notificationPO = await hostAppPO.findNotification('e2e-fbf7ec24f2d4-notification'); + await expect(notificationPO.getSeverity()).toEqual('error'); + await notificationPO.close(); + } + }); + + it('should apply specified duration', async () => { + await testingViewPO.navigateTo(); + const notificationPanelPO = await testingViewPO.openNotificationPanel(); + await notificationPanelPO.enterCssClass('e2e-fbf7ec24f2d4-notification'); + + // short + await notificationPanelPO.selectDuration('short'); + await notificationPanelPO.show(); + { + const notificationPO = await hostAppPO.findNotification('e2e-fbf7ec24f2d4-notification'); + await expect(notificationPO.getDuration()).toEqual('short'); + await notificationPO.close(); + } + + // medium + await notificationPanelPO.selectDuration('medium'); + await notificationPanelPO.show(); + { + const notificationPO = await hostAppPO.findNotification('e2e-fbf7ec24f2d4-notification'); + await expect(notificationPO.getDuration()).toEqual('medium'); + await notificationPO.close(); + } + + // long + await notificationPanelPO.selectDuration('long'); + await notificationPanelPO.show(); + { + const notificationPO = await hostAppPO.findNotification('e2e-fbf7ec24f2d4-notification'); + await expect(notificationPO.getDuration()).toEqual('long'); + await notificationPO.close(); + } + + // infinite + await notificationPanelPO.selectDuration('infinite'); + await notificationPanelPO.show(); + { + const notificationPO = await hostAppPO.findNotification('e2e-fbf7ec24f2d4-notification'); + await expect(notificationPO.getDuration()).toBeNull(); + await notificationPO.close(); + } + }); + + it('should allow to close a notification', async () => { + await testingViewPO.navigateTo(); + const notificationPanelPO = await testingViewPO.openNotificationPanel(); + await notificationPanelPO.enterCssClass('e2e-fbf7ec24f2d4-notification'); + + await notificationPanelPO.show(); + await expect(hostAppPO.findNotification('e2e-fbf7ec24f2d4-notification')).not.toBeNull('expected notification to show'); + + const notificationPO = await hostAppPO.findNotification('e2e-fbf7ec24f2d4-notification'); + await notificationPO.close(); + await expect(hostAppPO.findNotification('e2e-fbf7ec24f2d4-notification')).toBeNull('expected notification to be closed'); + }); + + it('should allow to show multiple notifications', async () => { + await testingViewPO.navigateTo(); + const notificationPanelPO = await testingViewPO.openNotificationPanel(); + + await notificationPanelPO.enterCssClass('e2e-6dbbd7fe35c0-notification'); + await notificationPanelPO.show(); + + await notificationPanelPO.enterCssClass('e2e-b5e3e1a38c3d-notification'); + await notificationPanelPO.show(); + + await expect(hostAppPO.findNotification('e2e-6dbbd7fe35c0-notification')).not.toBeNull('expected notification \'6dbbd7fe35c0\' to show'); + await expect(hostAppPO.findNotification('e2e-b5e3e1a38c3d-notification')).not.toBeNull('expected notification \'b5e3e1a38c3d\' to show'); + }); + + it('should allow to replace notifications', async () => { + await testingViewPO.navigateTo(); + + const notificationPanelPO = await testingViewPO.openNotificationPanel(); + + await notificationPanelPO.enterCssClass('e2e-first-notification'); + await notificationPanelPO.enterText('first'); + await notificationPanelPO.enterGroup('group-identity'); + await notificationPanelPO.show(); + { + const notificationPO = await hostAppPO.findNotification('e2e-first-notification'); + await expect(notificationPO.getText()).toEqual('first'); + } + + await notificationPanelPO.enterCssClass('e2e-second-notification'); + await notificationPanelPO.enterText('second'); + await notificationPanelPO.enterGroup('group-identity'); + await notificationPanelPO.show(); + + await expect(hostAppPO.getNotificationCount()).toEqual(1); + + { + const notificationPO = await hostAppPO.findNotification('e2e-first-notification'); + await expect(notificationPO).toBeNull('expected notification \'first\' not to show'); + } + { + const notificationPO = await hostAppPO.findNotification('e2e-second-notification'); + await expect(notificationPO).not.toBeNull('expected notification \'second\' to show'); + await expect(notificationPO.getText()).toEqual('second'); + } + }); + + it('should show a custom notification [testcase: 61097bad]', async () => { + await testingViewPO.navigateTo(); + const notificationPanelPO = await testingViewPO.openNotificationPanel(); + + await notificationPanelPO.enterQualifier({'type': 'list'}); + await notificationPanelPO.enterCssClass('e2e-61097bad-notification'); + await notificationPanelPO.enterPayload({'items': ['a', 'b', 'c']}); + + await notificationPanelPO.show(); + const notificationPO = new Testcase61097badNotificationPO('e2e-61097bad-notification'); + await expect(notificationPO.getItems()).toEqual(['a', 'b', 'c']); + }); +}); diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/activity-actions-panel.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/activity-actions-panel.po.ts new file mode 100644 index 000000000..1712676aa --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/activity-actions-panel.po.ts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { checkCheckbox, switchToIFrameContext } from '../util/testing.util'; +import { ActivityActionPO, HostAppPO } from './host-app.po'; + +export class ActivityActionsPanelPo { + + private _panel = $('app-activity-actions-panel'); + + constructor(public iframeContext: string[]) { + } + + public async checkShowUrlOpenActivityAction(check: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + await checkCheckbox(check, this._panel.$('input#show-url-open-activity-action')); + } + + public async checkCustomActivityAction(check: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + await checkCheckbox(check, this._panel.$('input#show-custom-activity-action')); + } + + public async findUrlOpenActivityAction(): Promise { + return new HostAppPO().findActivityAction('e2e-url-open-activity-action'); + } + + public async findCustomActivityAction(): Promise { + return new HostAppPO().findActivityAction('e2e-custom-activity-action'); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/activity-interaction-panel.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/activity-interaction-panel.po.ts new file mode 100644 index 000000000..c4718effe --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/activity-interaction-panel.po.ts @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; + +export class ActivityInteractionPanelPO { + + private _panel = $('app-activity-interaction-panel'); + + constructor(public iframeContext: string[]) { + } + + public async enterTitle(title: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#title'); + await inputField.clear(); + await inputField.sendKeys(title); + return Promise.resolve(); + } + + public async enterItemText(itemText: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#item-text'); + await inputField.clear(); + await inputField.sendKeys(itemText); + return Promise.resolve(); + } + + public async enterItemCssClass(itemCssClass: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#item-css-class'); + await inputField.clear(); + await inputField.sendKeys(itemCssClass); + return Promise.resolve(); + } + + public async enterDeltaPx(deltaPx: number): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#panel-width-delta'); + await inputField.clear(); + await inputField.sendKeys(deltaPx); + return Promise.resolve(); + } + + public async getActiveLog(): Promise { + await switchToIFrameContext(this.iframeContext); + const activeLog: string = await this._panel.$('textarea#active-log').getAttribute('value'); + return activeLog.split(/\s+/).map(activeLogEntry => JSON.parse(activeLogEntry)); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/host-app.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/host-app.po.ts new file mode 100644 index 000000000..ee586aa36 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/host-app.po.ts @@ -0,0 +1,455 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $, $$, browser, ElementFinder, protractor } from 'protractor'; +import { getCssClasses, switchToMainContext } from '../util/testing.util'; +import { Duration, Severity } from '@scion/workbench-application-platform.api'; +import { ISize } from 'selenium-webdriver'; + +export class HostAppPO { + + /** + * Finds the view tab which has given CSS class set. + * If not found, the promise resolves to `null`. + */ + public async findViewTab(cssClass: string): Promise { + await switchToMainContext(); + const viewTabFinder = $(`wb-view-tab.${cssClass}`); + + const exists = await viewTabFinder.isPresent(); + if (!exists) { + return null; + } + + return new class implements ViewTabPO { + async click(): Promise { + await switchToMainContext(); + await viewTabFinder.click(); + } + + async close(): Promise { + await switchToMainContext(); + + // hover the view-tab to make the close button visible + await browser.actions().mouseMove(viewTabFinder).perform(); + await viewTabFinder.$('.e2e-close').click(); + } + + async getTitle(): Promise { + await switchToMainContext(); + return viewTabFinder.$('.e2e-title').getText(); + } + + async getHeading(): Promise { + await switchToMainContext(); + return viewTabFinder.$('.e2e-heading').getText(); + } + + async isDirty(): Promise { + await switchToMainContext(); + const element = viewTabFinder.$('.e2e-dirty'); + return await element.isPresent() && await element.isDisplayed(); + } + + async isClosable(): Promise { + await switchToMainContext(); + const element = viewTabFinder.$('.e2e-close'); + return await element.isPresent() && await element.isDisplayed(); + } + }; + } + + /** + * Returns the number of view tabs. + */ + public async getViewTabCount(): Promise { + await switchToMainContext(); + return $$('wb-view-tab').count(); + } + + /** + * Returns the number of notifications. + */ + public async getNotificationCount(): Promise { + await switchToMainContext(); + return $$('wb-notification').count(); + } + + /** + * Finds the notification which has given CSS class set. + * If not found, the promise resolves to `null`. + */ + public async findNotification(cssClass: string): Promise { + await switchToMainContext(); + const notificationFinder = $(`wb-notification.${cssClass}`); + + const exists = await notificationFinder.isPresent(); + if (!exists) { + return null; + } + + return new class implements NotificationPO { + async getTitle(): Promise { + await switchToMainContext(); + return notificationFinder.$('.e2e-title').getText(); + } + + async getText(): Promise { + await switchToMainContext(); + return notificationFinder.$('.e2e-text').getText(); + } + + async getSeverity(): Promise { + await switchToMainContext(); + + const cssClasses = await getCssClasses(notificationFinder); + if (cssClasses.includes('e2e-severity-info')) { + return 'info'; + } + else if (cssClasses.includes('e2e-severity-warn')) { + return 'warn'; + } + else if (cssClasses.includes('e2e-severity-error')) { + return 'error'; + } + return null; + } + + async getDuration(): Promise { + await switchToMainContext(); + + const cssClasses = await getCssClasses(notificationFinder); + if (cssClasses.includes('e2e-duration-short')) { + return 'short'; + } + else if (cssClasses.includes('e2e-duration-medium')) { + return 'medium'; + } + else if (cssClasses.includes('e2e-duration-long')) { + return 'long'; + } + else if (cssClasses.includes('infinite')) { + return 'infinite'; + } + return null; + } + + async close(): Promise { + await switchToMainContext(); + await notificationFinder.$('.e2e-close').click(); + // wait until the animation completes + await browser.wait(protractor.ExpectedConditions.stalenessOf(notificationFinder), 5000); + } + }; + } + + /** + * Finds the notification which has given CSS class set. + * If not found, the promise resolves to `null`. + */ + public async findMessageBox(cssClass: string): Promise { + await switchToMainContext(); + const msgboxFinder = $(`wb-message-box.${cssClass}`); + + const exists = await msgboxFinder.isPresent(); + if (!exists) { + return null; + } + + return new class implements MessageBoxPO { + async getTitle(): Promise { + await switchToMainContext(); + return msgboxFinder.$('.e2e-title').getText(); + } + + async getText(): Promise { + await switchToMainContext(); + return msgboxFinder.$('.e2e-text').getText(); + } + + async getSeverity(): Promise { + await switchToMainContext(); + const cssClasses = await getCssClasses(msgboxFinder); + if (cssClasses.includes('e2e-severity-info')) { + return 'info'; + } + else if (cssClasses.includes('e2e-severity-warn')) { + return 'warn'; + } + else if (cssClasses.includes('e2e-severity-error')) { + return 'error'; + } + return null; + } + + async getModality(): Promise<'application' | 'view' | null> { + await switchToMainContext(); + const cssClasses = await getCssClasses(msgboxFinder); + if (cssClasses.includes('e2e-modality-application')) { + return 'application'; + } + else if (cssClasses.includes('e2e-severity-view')) { + return 'view'; + } + return null; + } + + async isContentSelectable(): Promise { + await switchToMainContext(); + + const text = await msgboxFinder.$('.e2e-text').getText(); + + await browser.actions().mouseMove(msgboxFinder.$('.e2e-text')).perform(); + await browser.actions().doubleClick().perform(); + const selection: string = await browser.executeScript('return window.getSelection().toString();') as string; + + return selection && selection.length && text.includes(selection); + } + + async getActions(): Promise<{ [key: string]: string }> { + await switchToMainContext(); + const actions: { [key: string]: string } = {}; + + const actionsFinder = msgboxFinder.$$('button.e2e-action'); + const count = await actionsFinder.count(); + for (let i = 0; i < count; i++) { + const action: ElementFinder = await actionsFinder.get(i); + const cssClasses = await getCssClasses(action); + const actionKey = cssClasses.find(candidate => candidate.startsWith('e2e-action-key-')); + actions[actionKey.substr('e2e-action-key-'.length)] = await action.getText(); + } + + return actions; + } + + async close(action: string): Promise { + await switchToMainContext(); + await msgboxFinder.$(`button.e2e-action.e2e-action-key-${action}`).click(); + // wait until the animation completes + await browser.wait(protractor.ExpectedConditions.stalenessOf(msgboxFinder), 5000); + } + + async isDisplayed(): Promise { + await switchToMainContext(); + return msgboxFinder.isDisplayed(); + } + }; + } + + /** + * Clicks the activity item which has given CSS class set. + * + * The promise returned is rejected if not found. + */ + public async clickActivityItem(cssClass: string): Promise { + await switchToMainContext(); + const activityItemPO = await this.findActivityItem(cssClass); + if (activityItemPO === null) { + return Promise.reject(`Activity item not found [cssClass=${cssClass}]`); + } + await activityItemPO.click(); + } + + /** + * Clicks the view tab which has given CSS class set. + * + * The promise returned is rejected if not found. + */ + public async clickViewTab(cssClass: string): Promise { + await switchToMainContext(); + const viewTabPO = await this.findViewTab(cssClass); + if (viewTabPO === null) { + return Promise.reject(`View tab not found [cssClass=${cssClass}]`); + } + await viewTabPO.click(); + } + + /** + * Finds the activity item which has given CSS class set. + * If not found, the promise resolves to `null`. + */ + public async findActivityItem(cssClass: string): Promise { + await switchToMainContext(); + const activityItemFinder = $(`wb-activity-part .e2e-activity-bar a.e2e-activity-item.${cssClass}`); + const activityPanelFinder = $(`wb-activity-part .e2e-activity-panel.${cssClass}`); + + const exists = await activityItemFinder.isPresent(); + if (!exists) { + return null; + } + + return new class implements ActivityItemPO { + async getTitle(): Promise { + await switchToMainContext(); + return await activityItemFinder.getAttribute('title'); + } + + async getText(): Promise { + await switchToMainContext(); + return await activityItemFinder.getText(); + } + + async getCssClasses(): Promise { + await switchToMainContext(); + return await getCssClasses(activityItemFinder); + } + + async click(): Promise { + await switchToMainContext(); + + const cssClasses = await this.getCssClasses(); + const closePanel = cssClasses.includes('e2e-active'); + await activityItemFinder.click(); + // wait until the animation completes + closePanel && await browser.wait(protractor.ExpectedConditions.stalenessOf(activityPanelFinder), 5000); + } + }; + } + + /** + * Finds the activity panel which has given CSS class set. + * If not found, the promise resolves to `null`. + */ + public async findActivityPanel(cssClass: string): Promise { + await switchToMainContext(); + const activityPanelFinder = $(`wb-activity-part .e2e-activity-panel.${cssClass}`); + + const exists = await activityPanelFinder.isPresent(); + if (!exists) { + return null; + } + + return new class implements ActivityPanelPO { + async getTitle(): Promise { + await switchToMainContext(); + return activityPanelFinder.$('.e2e-activity-title').getText(); + } + + async getSize(): Promise { + await switchToMainContext(); + return activityPanelFinder.getSize(); + } + + async getCssClasses(): Promise { + await switchToMainContext(); + return getCssClasses(activityPanelFinder); + } + }; + } + + /** + * Enlarges or shrinks the activity panel. + */ + public async moveActivitySash(delta: number): Promise { + await switchToMainContext(); + await browser.actions().mouseMove($('div.e2e-activity-sash'), {x: 0, y: 0}).perform(); + await browser.actions() + .mouseDown() + .mouseMove({x: delta, y: 0}) + .mouseUp() + .perform(); + } + + /** + * Finds the activity action which has given CSS class set. + */ + public async findActivityAction(cssClass: string): Promise { + const actionFinder = $(`wb-activity-part .e2e-activity-actions .${cssClass}`); + + return new class implements ActivityActionPO { + async isPresent(): Promise { + await switchToMainContext(); + return actionFinder.isPresent(); + } + + async click(): Promise { + await switchToMainContext(); + await actionFinder.click(); + } + }; + } +} + +export interface ViewTabPO { + getTitle(): Promise; + + getHeading(): Promise; + + isDirty(): Promise; + + isClosable(): Promise; + + close(): Promise; + + click(): Promise; +} + +export interface NotificationPO { + getTitle(): Promise; + + getText(): Promise; + + getSeverity(): Promise; + + getDuration(): Promise; + + close(): Promise; +} + +export interface MessageBoxPO { + getTitle(): Promise; + + getText(): Promise; + + getSeverity(): Promise; + + getModality(): Promise<'view' | 'application' | null>; + + isContentSelectable(): Promise; + + getActions(): Promise<{ [key: string]: string }>; + + close(action: string): Promise; + + isDisplayed(): Promise; +} + +/** + * Represents an activity item in the activity bar. + */ +export interface ActivityItemPO { + getTitle(): Promise; + + getText(): Promise; + + getCssClasses(): Promise; + + click(): Promise; +} + +/** + * Represents an activity panel in the activity part. + */ +export interface ActivityPanelPO { + getTitle(): Promise; + + getSize(): Promise; + + getCssClasses(): Promise; +} + +/* +* Represents a clickable activity action +*/ +export interface ActivityActionPO { + isPresent(): Promise; + + click(): Promise; +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/message-box-panel.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/message-box-panel.po.ts new file mode 100644 index 000000000..0bf001f91 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/message-box-panel.po.ts @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $, browser, protractor } from 'protractor'; +import { checkCheckbox, selectOption, switchToIFrameContext, switchToMainContext } from '../util/testing.util'; +import { Qualifier, Severity } from '@scion/workbench-application-platform.api'; +import { SciParamsEnterPanelPO } from './sci-params-enter.po'; + +export class MessageBoxPanelPO { + + private _panel = $('app-message-box-panel'); + private _cssClasses: string[] = []; + + constructor(public iframeContext: string[]) { + } + + public async enterQualifier(qualifier: Qualifier): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(qualifier, this._panel.$('.e2e-qualifier-panel')); + } + + public async enterTitle(label: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#title'); + await inputField.clear(); + await inputField.sendKeys(label); + return Promise.resolve(); + } + + public async enterText(text: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#text'); + await inputField.clear(); + await inputField.sendKeys(text); + return Promise.resolve(); + } + + public async selectSeverity(value: Severity): Promise { + await switchToIFrameContext(this.iframeContext); + await selectOption(value, this._panel.$('select#severity')); + } + + public async selectModality(value: 'view' | 'application'): Promise { + await switchToIFrameContext(this.iframeContext); + await selectOption(value, this._panel.$('select#modality')); + } + + public async checkContentSelectable(check: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + await checkCheckbox(check, this._panel.$('#content-selectable')); + } + + public async enterCssClass(cssClass: string): Promise { + this._cssClasses = cssClass.split(/\s+/); + + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#css-class'); + await inputField.clear(); + await inputField.sendKeys(cssClass); + return Promise.resolve(); + } + + /** + * Enters given JSON object into the payload field. + */ + public async enterPayload(payload: any): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = $('#payload'); + await inputField.clear(); + await inputField.sendKeys(JSON.stringify(payload)); + return Promise.resolve(); + } + + public async enterActions(actions: { [key: string]: string }): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(actions, this._panel.$('.e2e-action-panel')); + } + + public async getCloseAction(): Promise { + await switchToIFrameContext(this.iframeContext); + return this._panel.$('output.e2e-close-action').getText(); + } + + public async open(): Promise { + await switchToIFrameContext(this.iframeContext); + await this._panel.$('button.e2e-open').click(); + + // wait until the animation completes + if (this._cssClasses.length) { + await switchToMainContext(); + await browser.wait(protractor.ExpectedConditions.elementToBeClickable($(`wb-message-box.${this._cssClasses.join('.')}`)), 5000); + } + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/notification-panel.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/notification-panel.po.ts new file mode 100644 index 000000000..0d653fc37 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/notification-panel.po.ts @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $, browser, protractor } from 'protractor'; +import { selectOption, switchToIFrameContext, switchToMainContext } from '../util/testing.util'; +import { Duration, Qualifier, Severity } from '@scion/workbench-application-platform.api'; +import { SciParamsEnterPanelPO } from './sci-params-enter.po'; + +export class NotificationPanelPO { + + private _panel = $('app-notification-panel'); + private _cssClasses: string[] = []; + + constructor(public iframeContext: string[]) { + } + + public async enterQualifier(qualifier: Qualifier): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(qualifier, this._panel.$('.e2e-qualifier-panel')); + } + + public async enterTitle(label: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#title'); + await inputField.clear(); + await inputField.sendKeys(label); + return Promise.resolve(); + } + + public async enterText(text: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#text'); + await inputField.clear(); + await inputField.sendKeys(text); + return Promise.resolve(); + } + + public async selectSeverity(value: Severity): Promise { + await switchToIFrameContext(this.iframeContext); + await selectOption(value, this._panel.$('select#severity')); + } + + public async selectDuration(value: Duration): Promise { + await switchToIFrameContext(this.iframeContext); + await selectOption(value, this._panel.$('select#duration')); + } + + public async enterGroup(group: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#group'); + await inputField.clear(); + await inputField.sendKeys(group); + return Promise.resolve(); + } + + public async enterCssClass(cssClass: string): Promise { + this._cssClasses = cssClass.split(/\s+/); + + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#css-class'); + await inputField.clear(); + await inputField.sendKeys(cssClass); + return Promise.resolve(); + } + + /** + * Enters given JSON object into the payload field. + */ + public async enterPayload(payload: any): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = $('#payload'); + await inputField.clear(); + await inputField.sendKeys(JSON.stringify(payload)); + return Promise.resolve(); + } + + public async show(): Promise { + await switchToIFrameContext(this.iframeContext); + await this._panel.$('button.e2e-show').click(); + + // wait until the animation completes + if (this._cssClasses.length) { + await switchToMainContext(); + await browser.wait(protractor.ExpectedConditions.elementToBeClickable($(`wb-notification.${this._cssClasses.join('.')}`)), 5000); + } + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/popup-open-activity-action-panel.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/popup-open-activity-action-panel.po.ts new file mode 100644 index 000000000..36ce60218 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/popup-open-activity-action-panel.po.ts @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { checkCheckbox, switchToIFrameContext } from '../util/testing.util'; +import { Qualifier } from '@scion/workbench-application-platform.api'; +import { Params } from '@angular/router'; +import { SciParamsEnterPanelPO } from './sci-params-enter.po'; + +export class PopupOpenActivityActionPanelPO { + + private _panel = $('app-popup-open-activity-action-panel'); + + constructor(public iframeContext: string[]) { + } + + public async enterLabel(label: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#label'); + await inputField.clear(); + await inputField.sendKeys(label); + return Promise.resolve(); + } + + public async enterTitle(label: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#title'); + await inputField.clear(); + await inputField.sendKeys(label); + return Promise.resolve(); + } + + public async enterCssClass(cssClass: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#css-class'); + await inputField.clear(); + await inputField.sendKeys(cssClass); + return Promise.resolve(); + } + + public async enterQualifier(qualifier: Qualifier): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(qualifier, this._panel.$('.e2e-qualifier-panel')); + } + + public async enterMatrixParams(params: Params): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(params, this._panel.$('.e2e-matrix-params-panel')); + } + + public async enterQueryParams(params: Params): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(params, this._panel.$('.e2e-query-params-panel')); + } + + public async checkCloseOnFocusLost(check: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + await checkCheckbox(check, this._panel.$('#onFocusLost')); + } + + public async checkCloseOnEscape(check: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + await checkCheckbox(check, this._panel.$('#onEscape')); + } + + public async checkCloseOnGridLayoutChange(check: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + await checkCheckbox(check, this._panel.$('#onGridLayoutChange')); + } + + public async addAction(): Promise { + await switchToIFrameContext(this.iframeContext); + await this._panel.$('button.e2e-add-action').click(); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/popup-panel.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/popup-panel.po.ts new file mode 100644 index 000000000..eab373e6a --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/popup-panel.po.ts @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { checkCheckbox, selectOption, switchToIFrameContext } from '../util/testing.util'; +import { Qualifier } from '@scion/workbench-application-platform.api'; +import { Params } from '@angular/router'; +import { SciParamsEnterPanelPO } from './sci-params-enter.po'; +import { SciPropertyPanelPO } from './sci-property.po'; + +export class PopupPanelPO { + + private _panel = $('app-popup-panel'); + + constructor(public iframeContext: string[]) { + } + + public async enterQualifier(qualifier: Qualifier): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(qualifier, this._panel.$('.e2e-qualifier-panel')); + } + + public async enterMatrixParams(params: Params): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(params, this._panel.$('.e2e-matrix-params-panel')); + } + + public async enterQueryParams(params: Params): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(params, this._panel.$('.e2e-query-params-panel')); + } + + public async selectPosition(value: 'north' | 'east' | 'south' | 'west'): Promise { + await switchToIFrameContext(this.iframeContext); + await selectOption(value, this._panel.$('select#position')); + } + + public async checkCloseOnFocusLost(check: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + await checkCheckbox(check, this._panel.$('#onFocusLost')); + } + + public async checkCloseOnEscape(check: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + await checkCheckbox(check, this._panel.$('#onEscape')); + } + + public async checkCloseOnGridLayoutChange(check: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + await checkCheckbox(check, this._panel.$('#onGridLayoutChange')); + } + + public async execute(): Promise { + await switchToIFrameContext(this.iframeContext); + await this._panel.$('button.e2e-execute').click(); + } + + public async getResult(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(this.iframeContext); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-result')); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/sci-accordion-p.o.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/sci-accordion-p.o.ts new file mode 100644 index 000000000..b60e0f786 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/sci-accordion-p.o.ts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { ElementFinder } from 'protractor'; +import { getCssClasses } from '../util/testing.util'; + +export class SciAccordionPO { + + /** + * Opens or closes the accordion item of given CSS class. + * If `open` is not specified, the item is toggled. + */ + public async toggle(sciAccordionFinder: ElementFinder, cssClass: string, open?: boolean): Promise { + const accordionItem = sciAccordionFinder.$(`section.e2e-accordion-item.${cssClass}`); + const cssClasses = await getCssClasses(accordionItem); + + if (open === undefined) { + open = !cssClasses.includes('e2e-expanded'); + } + + const doOpen = open && !cssClasses.includes('e2e-expanded'); + const doClose = !open && cssClasses.includes('e2e-expanded'); + + if (doOpen || doClose) { + await accordionItem.$('.e2e-accordion-item-header').click(); + } + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/sci-params-enter.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/sci-params-enter.po.ts new file mode 100644 index 000000000..72470b7af --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/sci-params-enter.po.ts @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { ElementFinder } from 'protractor'; + +export class SciParamsEnterPanelPO { + + /** + * Allows to enter parameters into '' panel. + */ + public async enterParams(params: Object, sciParamsEnterPanel: ElementFinder): Promise { + const addButton = sciParamsEnterPanel.$('button.e2e-add'); + const lastKeyInput = sciParamsEnterPanel.$$('input.e2e-key').last(); + const lastValueInput = sciParamsEnterPanel.$$('input.e2e-value').last(); + + for (const key of Object.keys(params)) { + await addButton.click(); + await lastKeyInput.sendKeys(key); + await lastValueInput.sendKeys(`${params[key]}`); + } + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/sci-property.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/sci-property.po.ts new file mode 100644 index 000000000..4a2324e31 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/sci-property.po.ts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { ElementFinder } from 'protractor'; + +export class SciPropertyPanelPO { + + /** + * Allows to read properties from '' panel. + */ + public async readProperties(sciPropertyPanel: ElementFinder): Promise<{ [key: string]: string }> { + const keysFinder = sciPropertyPanel.$$('.e2e-key'); + const valuesFinder = sciPropertyPanel.$$('.e2e-value'); + + const propertyCount = await keysFinder.count(); + if (propertyCount === 0) { + return null; + } + + const properties: { [key: string]: string } = {}; + for (let i = 0; i < propertyCount; i++) { + const key = await keysFinder.get(i).getText(); + const value = await valuesFinder.get(i).getText(); + properties[key] = value; + } + + return properties; + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-159913ad-popup.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-159913ad-popup.po.ts new file mode 100644 index 000000000..9ddd09423 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-159913ad-popup.po.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; + +const E2E_TESTING_POPUP_CONTEXT: string[] = ['e2e-testing-app', 'e2e-popup', 'e2e-popup-159913ad']; + +export class Testcase159913adPopupPO { + + /** + * Reads URL parameters. + */ + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + /** + * Reads URL query parameters. + */ + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-28f32b51-activity.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-28f32b51-activity.po.ts new file mode 100644 index 000000000..4e0ffe0bc --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-28f32b51-activity.po.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; + +const E2E_TESTING_ACTIVITY_CONTEXT: string[] = ['e2e-testing-app', 'e2e-activity', 'e2e-activity-28f32b51']; + +export class Testcase28f32b51ActivityPO { + + public async readActiveLog(): Promise { + await switchToIFrameContext(E2E_TESTING_ACTIVITY_CONTEXT); + const activeLog: string = await $('textarea#active-log').getAttribute('value'); + return activeLog.split(/\s+/).map(activeLogEntry => JSON.parse(activeLogEntry)); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-28f32b51-view.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-28f32b51-view.po.ts new file mode 100644 index 000000000..1c960c140 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-28f32b51-view.po.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; + +const E2E_TESTING_VIEW_CONTEXT: string[] = ['e2e-testing-app', 'e2e-view', 'e2e-view-28f32b51']; + +export class Testcase28f32b51ViewPO { + + public async readActiveLog(): Promise { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + const activeLog: string = await $('textarea#active-log').getAttribute('value'); + return activeLog.split(/\s+/).map(activeLogEntry => JSON.parse(activeLogEntry)); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-45dc693f-popup.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-45dc693f-popup.po.ts new file mode 100644 index 000000000..a0abb0074 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-45dc693f-popup.po.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; + +const E2E_TESTING_POPUP_CONTEXT: string[] = ['e2e-testing-app', 'e2e-popup', 'e2e-popup-45dc693f']; + +export class Testcase45dc693fPopupPO { + + /** + * Reads URL parameters. + */ + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + /** + * Reads URL query parameters. + */ + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-4a3a8984-activity-po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-4a3a8984-activity-po.ts new file mode 100644 index 000000000..a9b61c8c9 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-4a3a8984-activity-po.ts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; + +const E2E_TESTING_ACTIVITY_CONTEXT: string[] = ['e2e-testing-app', 'e2e-activity', 'e2e-activity-4a3a8984']; + +export class Testcase4a3a8984ActivityPO { + + public async getComponentInstanceUuid(): Promise { + await switchToIFrameContext(E2E_TESTING_ACTIVITY_CONTEXT); + return await $('.e2e-component-instance-uuid').getText(); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-56657ad1-view.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-56657ad1-view.po.ts new file mode 100644 index 000000000..672a93d60 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-56657ad1-view.po.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; + +const E2E_TESTING_VIEW_CONTEXT: string[] = ['e2e-testing-app', 'e2e-view', 'e2e-view-56657ad1']; + +export class Testcase56657ad1ViewPO { + + /** + * Reads URL parameters. + */ + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + /** + * Reads URL query parameters. + */ + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-5782ab19-activity.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-5782ab19-activity.po.ts new file mode 100644 index 000000000..71a1b154b --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-5782ab19-activity.po.ts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { browser, protractor } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; + +const E2E_TESTING_ACTIVITY_CONTEXT: string[] = ['e2e-testing-app', 'e2e-activity', 'e2e-activity-5782ab19']; + +export class Testcase5782ab19ActivityPO { + + /** + * Returns if given element is the active element. + */ + public async isActiveElement(fieldId: string): Promise { + await switchToIFrameContext(E2E_TESTING_ACTIVITY_CONTEXT); + const activeElementId = await browser.driver.switchTo().activeElement().getAttribute('id'); + return activeElementId === fieldId; + } + + public async pressTab(): Promise { + await switchToIFrameContext(E2E_TESTING_ACTIVITY_CONTEXT); + await browser.actions().sendKeys(protractor.Key.TAB).perform(); + } + + public async pressShiftTab(): Promise { + await switchToIFrameContext(E2E_TESTING_ACTIVITY_CONTEXT); + await browser.actions().sendKeys(protractor.Key.SHIFT, protractor.Key.TAB, protractor.Key.SHIFT).perform(); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-5782ab19-popup.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-5782ab19-popup.po.ts new file mode 100644 index 000000000..ef1bdc653 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-5782ab19-popup.po.ts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { browser, protractor } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; + +const E2E_TESTING_POPUP_CONTEXT: string[] = ['e2e-testing-app', 'e2e-popup', 'e2e-popup-5782ab19']; + +export class Testcase5782ab19PopupPO { + + /** + * Returns if given element is the active element. + */ + public async isActiveElement(fieldId: string): Promise { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + const activeElementId = await browser.driver.switchTo().activeElement().getAttribute('id'); + return activeElementId === fieldId; + } + + public async pressTab(): Promise { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + await browser.actions().sendKeys(protractor.Key.TAB).perform(); + } + + public async pressShiftTab(): Promise { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + await browser.actions().sendKeys(protractor.Key.SHIFT, protractor.Key.TAB, protractor.Key.SHIFT).perform(); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-5782ab19-view.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-5782ab19-view.po.ts new file mode 100644 index 000000000..452410797 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-5782ab19-view.po.ts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { browser, protractor } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; + +const E2E_TESTING_VIEW_CONTEXT: string[] = ['e2e-testing-app', 'e2e-view', 'e2e-view-5782ab19']; + +export class Testcase5782ab19ViewPO { + + /** + * Returns if given element is the active element. + */ + public async isActiveElement(fieldId: string): Promise { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + const activeElementId = await browser.driver.switchTo().activeElement().getAttribute('id'); + return activeElementId === fieldId; + } + + public async pressTab(): Promise { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + await browser.actions().sendKeys(protractor.Key.TAB).perform(); + } + + public async pressShiftTab(): Promise { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + await browser.actions().sendKeys(protractor.Key.SHIFT, protractor.Key.TAB, protractor.Key.SHIFT).perform(); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-61097bad-msgbox.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-61097bad-msgbox.po.ts new file mode 100644 index 000000000..892983bb5 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-61097bad-msgbox.po.ts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToMainContext } from '../util/testing.util'; + +export class Testcase61097badMessageBoxPO { + + constructor(private _msgboxCssClass: string) { + } + + public async getItems(): Promise { + await switchToMainContext(); + const msgboxFinder = $(`wb-message-box.${this._msgboxCssClass} app-list-messagebox`); + const itemFinder = msgboxFinder.$$('.e2e-item'); + const itemCount = await itemFinder.count(); + + const items: string[] = []; + for (let i = 0; i < itemCount; i++) { + items.push(await itemFinder.get(i).getText()); + } + return items; + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-61097bad-notification.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-61097bad-notification.po.ts new file mode 100644 index 000000000..cf3b1494c --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-61097bad-notification.po.ts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToMainContext } from '../util/testing.util'; + +export class Testcase61097badNotificationPO { + + constructor(private _notificationCssClass: string) { + } + + public async getItems(): Promise { + await switchToMainContext(); + const notificationFinder = $(`wb-notification.${this._notificationCssClass} app-list-notification`); + const itemFinder = notificationFinder.$$('.e2e-item'); + const itemCount = await itemFinder.count(); + + const items: string[] = []; + for (let i = 0; i < itemCount; i++) { + items.push(await itemFinder.get(i).getText()); + } + return items; + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-68f302b4-view-po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-68f302b4-view-po.ts new file mode 100644 index 000000000..88a30cd14 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-68f302b4-view-po.ts @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; +import { ViewNavigationPanelPO } from './view-navigation-panel.po'; + +const E2E_TESTING_VIEW_CONTEXT: string[] = ['e2e-testing-app', 'e2e-view', 'e2e-view-68f302b4']; + +export class Testcase68f302b4ViewPO { + + public readonly viewNavigationPanelPO = new ViewNavigationPanelPO(E2E_TESTING_VIEW_CONTEXT); + + public async getAppInstanceUuid(): Promise { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return await $('.e2e-app-instance-uuid').getText(); + } + + public async getComponentInstanceUuid(): Promise { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return await $('.e2e-component-instance-uuid').getText(); + } + + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-8a468258-popup.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-8a468258-popup.po.ts new file mode 100644 index 000000000..d0505aa17 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-8a468258-popup.po.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; + +const E2E_TESTING_POPUP_CONTEXT: string[] = ['e2e-testing-app', 'e2e-popup', 'e2e-popup-8a468258']; + +export class Testcase8a468258PopupPO { + + /** + * Reads URL parameters. + */ + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + /** + * Reads URL query parameters. + */ + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-9c5319f7-popup.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-9c5319f7-popup.po.ts new file mode 100644 index 000000000..07dbf17ed --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-9c5319f7-popup.po.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; + +const E2E_TESTING_POPUP_CONTEXT: string[] = ['e2e-testing-app', 'e2e-popup', 'e2e-popup-9c5319f7']; + +export class Testcase9c5319f7PopupPO { + + /** + * Reads URL parameters. + */ + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + /** + * Reads URL query parameters. + */ + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-a686d615-view.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-a686d615-view.po.ts new file mode 100644 index 000000000..8772d8abd --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-a686d615-view.po.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; + +const E2E_TESTING_VIEW_CONTEXT: string[] = ['e2e-testing-app', 'e2e-view', 'e2e-view-a686d615']; + +export class TestcaseA686d615ViewPO { + + /** + * Reads URL parameters. + */ + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + /** + * Reads URL query parameters. + */ + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-b6a8fe23-view.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-b6a8fe23-view.po.ts new file mode 100644 index 000000000..21cca67f5 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-b6a8fe23-view.po.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; + +const E2E_TESTING_VIEW_CONTEXT: string[] = ['e2e-testing-app', 'e2e-view', 'e2e-view-b6a8fe23']; + +export class TestcaseB6a8fe23ViewPO { + + /** + * Reads URL parameters. + */ + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + /** + * Reads URL query parameters. + */ + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-c8e40918-view.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-c8e40918-view.po.ts new file mode 100644 index 000000000..38e330c6f --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-c8e40918-view.po.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; + +const E2E_TESTING_VIEW_CONTEXT: string[] = ['e2e-testing-app', 'e2e-view', 'e2e-view-c8e40918']; + +export class TestcaseC8e40918ViewPO { + + /** + * Reads URL parameters. + */ + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + /** + * Reads URL query parameters. + */ + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-cc977da9-view.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-cc977da9-view.po.ts new file mode 100644 index 000000000..ae58d8f97 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-cc977da9-view.po.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; + +const E2E_TESTING_VIEW_CONTEXT: string[] = ['e2e-testing-app', 'e2e-view', 'e2e-view-cc977da9']; + +export class TestcaseCc977da9ViewPO { + + /** + * Reads URL parameters. + */ + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + /** + * Reads URL query parameters. + */ + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-f4286ac4-popup.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-f4286ac4-popup.po.ts new file mode 100644 index 000000000..12dcd9e14 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-f4286ac4-popup.po.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; + +const E2E_TESTING_POPUP_CONTEXT: string[] = ['e2e-testing-app', 'e2e-popup', 'e2e-popup-f4286ac4']; + +export class TestcaseF4286ac4PopupPO { + + /** + * Reads URL parameters. + */ + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + /** + * Reads URL query parameters. + */ + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-fc077b32-popup.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-fc077b32-popup.po.ts new file mode 100644 index 000000000..5d88d75c4 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-fc077b32-popup.po.ts @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { getCssClasses, switchToIFrameContext, switchToMainContext } from '../util/testing.util'; + +const E2E_TESTING_POPUP_CONTEXT: string[] = ['e2e-testing-app', 'e2e-popup', 'e2e-popup-fc077b32']; + +export class Testcasefc077b32PopupPO { + + public async getPosition(): Promise<'north' | 'east' | 'south' | 'west' | null> { + await switchToMainContext(); + const cssClasses = await getCssClasses($(`.wb-popup.e2e-popup-fc077b32`)); + if (cssClasses.includes('e2e-position-north')) { + return 'north'; + } + else if (cssClasses.includes('e2e-position-east')) { + return 'east'; + } + else if (cssClasses.includes('e2e-position-south')) { + return 'south'; + } + else if (cssClasses.includes('e2e-position-west')) { + return 'west'; + } + return null; + } + + public async close(): Promise { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + await $('button#e2e-close').click(); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-ffd6a78f-view.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-ffd6a78f-view.po.ts new file mode 100644 index 000000000..ab08159c2 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testcase-ffd6a78f-view.po.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; + +const E2E_TESTING_VIEW_CONTEXT: string[] = ['e2e-testing-app', 'e2e-view', 'e2e-view-ffd6a78f']; + +export class TestcaseFfd6a78fViewPO { + + /** + * Reads URL parameters. + */ + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + /** + * Reads URL query parameters. + */ + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testing-activity.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testing-activity.po.ts new file mode 100644 index 000000000..2abe95ecf --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testing-activity.po.ts @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { HostAppPO } from './host-app.po'; +import { SciAccordionPO } from './sci-accordion-p.o'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; +import { ActivityInteractionPanelPO } from './activity-interaction-panel.po'; +import { ActivityActionsPanelPo } from './activity-actions-panel.po'; +import { ViewOpenActivityActionPanelPO } from './view-open-activity-action-panel.po'; +import { PopupOpenActivityActionPanelPO } from './popup-open-activity-action-panel.po'; + +export class TestingActivityPO { + + constructor(public iframeContext: string[]) { + } + + public async navigateTo(): Promise { + await new HostAppPO().clickActivityItem('e2e-testing-activity'); + } + + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(this.iframeContext); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(this.iframeContext); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } + + public async openActivityInteractionPanel(): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciAccordionPO().toggle($('sci-accordion'), 'e2e-activity-interaction-panel', true); + return new ActivityInteractionPanelPO(this.iframeContext); + } + + public async openActivityActionsPanel(): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciAccordionPO().toggle($('sci-accordion'), 'e2e-activity-actions-panel', true); + return new ActivityActionsPanelPo(this.iframeContext); + } + + public async openViewOpenActivityActionPanel(): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciAccordionPO().toggle($('sci-accordion'), 'e2e-add-view-open-activity-action', true); + return new ViewOpenActivityActionPanelPO(this.iframeContext); + } + + public async openPopupOpenActivityActionPanel(): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciAccordionPO().toggle($('sci-accordion'), 'e2e-add-popup-open-activity-action', true); + return new PopupOpenActivityActionPanelPO(this.iframeContext); + } + + public async closePopupOpenActivityActionPanel(): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciAccordionPO().toggle($('sci-accordion'), 'e2e-add-popup-open-activity-action', false); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testing-popup.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testing-popup.po.ts new file mode 100644 index 000000000..d94131aab --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testing-popup.po.ts @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $, browser, protractor } from 'protractor'; +import { switchToIFrameContext } from '../util/testing.util'; +import { SciPropertyPanelPO } from './sci-property.po'; + +const E2E_TESTING_POPUP_CONTEXT: string[] = ['e2e-testing-app', 'e2e-popup', 'e2e-testing-popup']; + +export class TestingPopupPO { + + /** + * Reads URL parameters. + */ + public async getUrlParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-params')); + } + + /** + * Reads URL query parameters. + */ + public async getUrlQueryParameters(): Promise<{ [key: string]: string }> { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + return new SciPropertyPanelPO().readProperties($('sci-property.e2e-url-query-params')); + } + + /** + * Enters given JSON object into the result field. + */ + public async enterResult(result: any): Promise { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + const inputField = $('textarea.e2e-result'); + await inputField.clear(); + await inputField.sendKeys(JSON.stringify(result)); + return Promise.resolve(); + } + + /** + * Closes the popup and returns the result. + */ + public async ok(): Promise { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + await $('button.e2e-ok').click(); + } + + /** + * Closes the popup without returning a result. + */ + public async escape(): Promise { + await switchToIFrameContext(E2E_TESTING_POPUP_CONTEXT); + await browser.actions().sendKeys(protractor.Key.ESCAPE).perform(); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/testing-view.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testing-view.po.ts new file mode 100644 index 000000000..7f8b5dd3a --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/testing-view.po.ts @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { ViewInteractionPanelPO } from './view-interaction-panel.po'; +import { ViewNavigationPanelPO } from './view-navigation-panel.po'; +import { HostAppPO } from './host-app.po'; +import { PopupPanelPO } from './popup-panel.po'; +import { SciAccordionPO } from './sci-accordion-p.o'; +import { switchToIFrameContext } from '../util/testing.util'; +import { MessageBoxPanelPO } from './message-box-panel.po'; +import { NotificationPanelPO } from './notification-panel.po'; + +const E2E_TESTING_VIEW_CONTEXT: string[] = ['e2e-testing-app', 'e2e-view', 'e2e-testing-view']; + +export class TestingViewPO { + + private _component = $('app-testing-view'); + + public async navigateTo(): Promise { + await new HostAppPO().clickActivityItem('e2e-open-testing-view'); + } + + public async openViewInteractionPanel(): Promise { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + await new SciAccordionPO().toggle(this._component.$('sci-accordion'), 'e2e-view-interaction-panel', true); + return new ViewInteractionPanelPO(E2E_TESTING_VIEW_CONTEXT); + } + + public async openViewNavigationPanel(): Promise { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + await new SciAccordionPO().toggle(this._component.$('sci-accordion'), 'e2e-view-navigation-panel', true); + return new ViewNavigationPanelPO(E2E_TESTING_VIEW_CONTEXT); + } + + public async openPopupPanel(): Promise { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + await new SciAccordionPO().toggle(this._component.$('sci-accordion'), 'e2e-popup-panel', true); + return new PopupPanelPO(E2E_TESTING_VIEW_CONTEXT); + } + + public async closePopupPanel(): Promise { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + await new SciAccordionPO().toggle(this._component.$('sci-accordion'), 'e2e-popup-panel', false); + } + + public async openMessageBoxPanel(): Promise { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + await new SciAccordionPO().toggle(this._component.$('sci-accordion'), 'e2e-message-box-panel', true); + return new MessageBoxPanelPO(E2E_TESTING_VIEW_CONTEXT); + } + + public async openNotificationPanel(): Promise { + await switchToIFrameContext(E2E_TESTING_VIEW_CONTEXT); + await new SciAccordionPO().toggle(this._component.$('sci-accordion'), 'e2e-notification-panel', true); + return new NotificationPanelPO(E2E_TESTING_VIEW_CONTEXT); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/view-interaction-panel.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/view-interaction-panel.po.ts new file mode 100644 index 000000000..3f60dd1ca --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/view-interaction-panel.po.ts @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { checkCheckbox, switchToIFrameContext } from '../util/testing.util'; + +export class ViewInteractionPanelPO { + + private _panel = $('app-view-interaction-panel'); + + constructor(public iframeContext: string[]) { + } + + public async enterTitle(title: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#title'); + await inputField.clear(); + await inputField.sendKeys(title); + return Promise.resolve(); + } + + public async enterHeading(heading: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#heading'); + await inputField.clear(); + await inputField.sendKeys(heading); + return Promise.resolve(); + } + + public async markDirty(dirty: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + const checkboxField = this._panel.$('input#dirty'); + await checkCheckbox(dirty, checkboxField); + return Promise.resolve(); + } + + public async setClosable(closable: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + const checkboxField = this._panel.$('input#closable'); + await checkCheckbox(closable, checkboxField); + return Promise.resolve(); + } + + public async close(): Promise { + await switchToIFrameContext(this.iframeContext); + await this._panel.$('button#close').click(); + } + + public async getActiveLog(): Promise { + await switchToIFrameContext(this.iframeContext); + const activeLog: string = await this._panel.$('textarea#active-log').getAttribute('value'); + return activeLog.split(/\s+/).map(activeLogEntry => JSON.parse(activeLogEntry)); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/view-navigation-panel.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/view-navigation-panel.po.ts new file mode 100644 index 000000000..7aecb6254 --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/view-navigation-panel.po.ts @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { checkCheckbox, selectOption, switchToIFrameContext } from '../util/testing.util'; +import { Qualifier } from '@scion/workbench-application-platform.api'; +import { Params } from '@angular/router'; +import { SciParamsEnterPanelPO } from './sci-params-enter.po'; + +export class ViewNavigationPanelPO { + + private _panel = $('app-view-navigation-panel'); + + constructor(public iframeContext: string[]) { + } + + public async enterQualifier(qualifier: Qualifier): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(qualifier, this._panel.$('.e2e-qualifier-panel')); + } + + public async enterMatrixParams(params: Params): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(params, this._panel.$('.e2e-matrix-params-panel')); + } + + public async enterQueryParams(params: Params): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(params, this._panel.$('.e2e-query-params-panel')); + } + + public async checkActivateIfPresent(check: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + await checkCheckbox(check, this._panel.$('#activateIfPresent')); + } + + public async checkCloseIfPresent(check: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + await checkCheckbox(check, this._panel.$('#closeIfPresent')); + } + + public async selectTarget(value: 'self' | 'blank'): Promise { + await switchToIFrameContext(this.iframeContext); + await selectOption(value, this._panel.$('select#target')); + } + + public async execute(): Promise { + await switchToIFrameContext(this.iframeContext); + await this._panel.$('button.e2e-execute').click(); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/page-object/view-open-activity-action-panel.po.ts b/projects/e2e/workbench-application-platform/test-runner/src/page-object/view-open-activity-action-panel.po.ts new file mode 100644 index 000000000..82d1c7bcc --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/page-object/view-open-activity-action-panel.po.ts @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms from the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { $ } from 'protractor'; +import { checkCheckbox, switchToIFrameContext } from '../util/testing.util'; +import { Qualifier } from '@scion/workbench-application-platform.api'; +import { Params } from '@angular/router'; +import { SciParamsEnterPanelPO } from './sci-params-enter.po'; + +export class ViewOpenActivityActionPanelPO { + + private _panel = $('app-view-open-activity-action-panel'); + + constructor(public iframeContext: string[]) { + } + + public async enterLabel(label: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#label'); + await inputField.clear(); + await inputField.sendKeys(label); + return Promise.resolve(); + } + + public async enterTitle(label: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#title'); + await inputField.clear(); + await inputField.sendKeys(label); + return Promise.resolve(); + } + + public async enterCssClass(cssClass: string): Promise { + await switchToIFrameContext(this.iframeContext); + const inputField = this._panel.$('input#css-class'); + await inputField.clear(); + await inputField.sendKeys(cssClass); + return Promise.resolve(); + } + + public async enterQualifier(qualifier: Qualifier): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(qualifier, this._panel.$('.e2e-qualifier-panel')); + } + + public async enterMatrixParams(params: Params): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(params, this._panel.$('.e2e-matrix-params-panel')); + } + + public async enterQueryParams(params: Params): Promise { + await switchToIFrameContext(this.iframeContext); + await new SciParamsEnterPanelPO().enterParams(params, this._panel.$('.e2e-query-params-panel')); + } + + public async checkActivateIfPresent(check: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + await checkCheckbox(check, this._panel.$('#activateIfPresent')); + } + + public async checkCloseIfPresent(check: boolean): Promise { + await switchToIFrameContext(this.iframeContext); + await checkCheckbox(check, this._panel.$('#closeIfPresent')); + } + + public async addAction(): Promise { + await switchToIFrameContext(this.iframeContext); + await this._panel.$('button.e2e-add-action').click(); + } +} diff --git a/projects/e2e/workbench-application-platform/test-runner/src/popup.e2e-spec.ts b/projects/e2e/workbench-application-platform/test-runner/src/popup.e2e-spec.ts new file mode 100644 index 000000000..da2f9558e --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/popup.e2e-spec.ts @@ -0,0 +1,481 @@ +/* + * Copyright (c) 2018 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { TestingViewPO } from './page-object/testing-view.po'; +import { HostAppPO } from './page-object/host-app.po'; +import { expectPopupToNotExist, expectPopupToShow } from './util/testing.util'; +import { browser, protractor } from 'protractor'; +import { Testcase9c5319f7PopupPO } from './page-object/testcase-9c5319f7-popup.po'; +import { Testcase45dc693fPopupPO } from './page-object/testcase-45dc693f-popup.po'; +import { TestcaseF4286ac4PopupPO } from './page-object/testcase-f4286ac4-popup.po'; +import { Testcase159913adPopupPO } from './page-object/testcase-159913ad-popup.po'; +import { Testcase8a468258PopupPO } from './page-object/testcase-8a468258-popup.po'; +import { Testcasefc077b32PopupPO } from './page-object/testcase-fc077b32-popup.po'; +import { TestingPopupPO } from './page-object/testing-popup.po'; +import { Testcase5782ab19PopupPO } from './page-object/testcase-5782ab19-popup.po'; + +describe('Popup', () => { + const hostAppPO = new HostAppPO(); + const testingViewPO = new TestingViewPO(); + + beforeEach(async () => { + await browser.get('/'); + }); + + describe('Navigation', () => { + + it('should allow navigation to private popups of the same application (implicit intent) [testcase: 1a90c8d2-popup]', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + testcase: '1a90c8d2-popup', + }); + await popupPanelPO.execute(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-1a90c8d2', componentSelector: 'app-popup-1a90c8d2'}); + }); + + it('should allow navigation to public popups of the same application (implicit intent) [testcase: 7330f506-popup]', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + testcase: '7330f506-popup', + }); + await popupPanelPO.execute(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-7330f506', componentSelector: 'app-popup-7330f506'}); + }); + + it('should allow navigation to public popups of other applications', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'communication', + action: 'create', + contactId: '5', + }); + await popupPanelPO.execute(); + + await expectPopupToShow({symbolicAppName: 'communication-app', popupCssClass: 'e2e-communication-create', componentSelector: 'app-communication-new-popup'}); + }); + + it('should not allow navigation to public popups of other applications if missing the intent', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'communication', + action: 'create', + contactId: '999', + }); + await popupPanelPO.execute(); + + const notificationPO = await hostAppPO.findNotification('e2e-not-qualified'); + await expect(notificationPO).not.toBeNull(); + await expect(notificationPO.getSeverity()).toEqual('error'); + await expectPopupToNotExist({symbolicAppName: 'communication-app', popupCssClass: 'e2e-communication-create'}); + }); + + it('should not allow navigation to private popups of other applications', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'contact', + action: 'create', + }); + await popupPanelPO.execute(); + + const notificationPO = await hostAppPO.findNotification('e2e-not-handled'); + await expect(notificationPO).not.toBeNull(); + await expect(notificationPO.getSeverity()).toEqual('error'); + await expectPopupToNotExist({symbolicAppName: 'contact-app', popupCssClass: 'e2e-contact'}); + }); + + it('should allow to provide query and matrix parameters [testcase: 9c5319f7-popup]', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + testcase: '9c5319f7-popup' + }); + await popupPanelPO.enterMatrixParams({ + mp1: '41ecdefec0a3', + mp2: 'da67bd554b36', + }); + await popupPanelPO.enterQueryParams({ + qp1: '6378a62700d3', + qp2: 'c5a37d3660ba', + }); + await popupPanelPO.execute(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-9c5319f7', componentSelector: 'app-popup-9c5319f7'}); + + const popupPO = new Testcase9c5319f7PopupPO(); + const urlParams = await popupPO.getUrlParameters(); + await expect(urlParams).toEqual({ + mp1: '41ecdefec0a3', + mp2: 'da67bd554b36', + }); + + const urlQueryParams = await popupPO.getUrlQueryParameters(); + await expect(urlQueryParams).toEqual({ + qp1: '6378a62700d3', + qp2: 'c5a37d3660ba', + }); + }); + + it('should allow to receive query and matrix parameters as specified in the manifest [testcase: 45dc693f-popup]', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + testcase: '45dc693f-popup', + }); + + await popupPanelPO.execute(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-45dc693f', componentSelector: 'app-popup-45dc693f'}); + + const popupPO = new Testcase45dc693fPopupPO(); + const urlParams = await popupPO.getUrlParameters(); + await expect(urlParams).toEqual({ + mp1: 'd52f5f88be27', + mp2: '01aa011fb2f4', + }); + + const urlQueryParams = await popupPO.getUrlQueryParameters(); + await expect(urlQueryParams).toEqual({ + qp1: 'f3227d5926c0', + qp2: '88a9cd0cb937', + }); + }); + + it('should allow to merge query and matrix parameters provided from intent (overwrite) and manifest [testcase: f4286ac4-popup]', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + testcase: 'f4286ac4-popup', + }); + await popupPanelPO.enterMatrixParams({ + mp1: '557c7323a13c', + mp2: '67dbebc8dd7c', + }); + await popupPanelPO.enterQueryParams({ + qp1: 'a3fa547519cf', + qp2: 'db03e5494054', + }); + await popupPanelPO.execute(); + + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-f4286ac4', componentSelector: 'app-popup-f4286ac4'}); + + const popupPO = new TestcaseF4286ac4PopupPO(); + const urlParams = await popupPO.getUrlParameters(); + await expect(urlParams).toEqual({ + mp1: '557c7323a13c', + mp2: '67dbebc8dd7c', + mpx: 'f406422c77fc', + }); + + const urlQueryParams = await popupPO.getUrlQueryParameters(); + await expect(urlQueryParams).toEqual({ + qp1: 'a3fa547519cf', + qp2: 'db03e5494054', + qpx: '18c9d6c51b0c', + }); + }); + + it('should substitute path parameters with values from the intent qualifier [testcase: 159913ad-popup]', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + testcase: '159913ad-popup', + qualifierParam1: 'e82bf49c4768', + qualifierParam2: '1b84a4a926f7' + }); + await popupPanelPO.execute(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-159913ad', componentSelector: 'app-popup-159913ad'}); + + const popupPO = new Testcase159913adPopupPO(); + const urlParams = await popupPO.getUrlParameters(); + await expect(urlParams).toEqual({ + a: 'e82bf49c4768', + b: '1b84a4a926f7', + }); + + const urlQueryParams = await popupPO.getUrlQueryParameters(); + await expect(urlQueryParams).toBeNull(); + }); + + it('should substitute matrix and query parameters with values from the intent qualifier [testcase: 8a468258-popup]', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + testcase: '8a468258-popup', + qualifierParam1: 'd8b74df2c77d', + qualifierParam2: 'e60c81360bee', + }); + await popupPanelPO.execute(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-8a468258', componentSelector: 'app-popup-8a468258'}); + + const popupPO = new Testcase8a468258PopupPO(); + const urlParams = await popupPO.getUrlParameters(); + await expect(urlParams).toEqual({ + matrixParam1: 'd8b74df2c77d', + matrixParam2: 'eda2e91468e1', + pathParam1: 'd8b74df2c77d', + }); + + const urlQueryParams = await popupPO.getUrlQueryParameters(); + await expect(urlQueryParams).toEqual({ + queryParam1: 'e60c81360bee', + queryParam2: '1a3d3aaf937e' + }); + }); + }); + + describe('Properties', () => { + it('should be positioned as specified [testcase: fc077b32-popup]', async () => { + await testingViewPO.navigateTo(); + const popupPO = new Testcasefc077b32PopupPO(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + testcase: 'fc077b32-popup' + }); + + await popupPanelPO.selectPosition('north'); + await popupPanelPO.execute(); + await expect(popupPO.getPosition()).toEqual('north'); + await popupPO.close(); + + await popupPanelPO.selectPosition('east'); + await popupPanelPO.execute(); + await expect(popupPO.getPosition()).toEqual('east'); + await popupPO.close(); + + await popupPanelPO.selectPosition('south'); + await popupPanelPO.execute(); + await expect(popupPO.getPosition()).toEqual('south'); + await popupPO.close(); + + await popupPanelPO.selectPosition('west'); + await popupPanelPO.execute(); + await expect(popupPO.getPosition()).toEqual('west'); + await popupPO.close(); + }); + + it('should close on focus lost', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + }); + await popupPanelPO.checkCloseOnFocusLost(true); + await popupPanelPO.execute(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + + // remove focus from the popup + await testingViewPO.closePopupPanel(); + await expectPopupToNotExist({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup'}); + }); + + it('should not close on focus lost', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + }); + await popupPanelPO.checkCloseOnFocusLost(false); + await popupPanelPO.execute(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + + // remove focus from the popup + await testingViewPO.closePopupPanel(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + }); + + it('should close on escape keystroke', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + }); + await popupPanelPO.checkCloseOnEscape(true); + await popupPanelPO.execute(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + + // send escape keystroke + await browser.actions().sendKeys(protractor.Key.ESCAPE).perform(); + await expectPopupToNotExist({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup'}); + }); + + it('should not close on escape keystroke', async () => { + await testingViewPO.navigateTo(); + + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + }); + await popupPanelPO.checkCloseOnEscape(false); + await popupPanelPO.execute(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + + // send escape keystroke + await browser.actions().sendKeys(protractor.Key.ESCAPE).perform(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + }); + + it('should close on grid layout change', async () => { + // Open activity to have a sash + await hostAppPO.clickActivityItem('e2e-contact-list'); + + // Open popup + await testingViewPO.navigateTo(); + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + }); + await popupPanelPO.checkCloseOnFocusLost(false); + await popupPanelPO.checkCloseOnGridLayoutChange(true); + await popupPanelPO.execute(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + + // Make a grid layout change + await hostAppPO.moveActivitySash(100); + await expectPopupToNotExist({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup'}); + }); + + it('should not close on grid layout change', async () => { + // Open activity to have a sash + await hostAppPO.clickActivityItem('e2e-contact-list'); + + // Open popup + await testingViewPO.navigateTo(); + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + }); + await popupPanelPO.checkCloseOnFocusLost(false); + await popupPanelPO.checkCloseOnGridLayoutChange(false); + await popupPanelPO.execute(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + + // Make a grid layout change + await hostAppPO.moveActivitySash(100); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + }); + }); + + describe('Interaction', () => { + + it('should return the popup result', async () => { + await testingViewPO.navigateTo(); + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + }); + await popupPanelPO.checkCloseOnFocusLost(false); + await popupPanelPO.checkCloseOnGridLayoutChange(false); + await popupPanelPO.execute(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-testing-popup', componentSelector: 'app-testing-popup'}); + + const json = { + value1: '948a14cc1e06', + value2: 'dd06acb924ed', + value3: 'ef247df0be6e', + }; + + const popupPO = new TestingPopupPO(); + await popupPO.enterResult(json); + await popupPO.ok(); + + await expect(popupPanelPO.getResult()).toEqual(json); + }); + }); + + describe('Focus', () => { + + it('should cycle the focus in the popup [testcase: 5782ab19-popup]', async () => { + await testingViewPO.navigateTo(); + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + testcase: '5782ab19-popup', + }); + await popupPanelPO.execute(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-5782ab19', componentSelector: 'app-popup-5782ab19'}); + + const popupPO = new Testcase5782ab19PopupPO(); + await expect(popupPO.isActiveElement('field-1')).toBeTruthy('(1)'); + + await popupPO.pressTab(); + await expect(popupPO.isActiveElement('field-2')).toBeTruthy('(2)'); + + await popupPO.pressTab(); + await expect(popupPO.isActiveElement('field-3')).toBeTruthy('(3)'); + + await popupPO.pressTab(); + await expect(popupPO.isActiveElement('field-1')).toBeTruthy('(4)'); + + await popupPO.pressTab(); + await expect(popupPO.isActiveElement('field-2')).toBeTruthy('(5)'); + + await popupPO.pressTab(); + await expect(popupPO.isActiveElement('field-3')).toBeTruthy('(6)'); + + await popupPO.pressShiftTab(); + await expect(popupPO.isActiveElement('field-2')).toBeTruthy('(7)'); + + await popupPO.pressShiftTab(); + await expect(popupPO.isActiveElement('field-1')).toBeTruthy('(8)'); + + await popupPO.pressShiftTab(); + await expect(popupPO.isActiveElement('field-3')).toBeTruthy('(9)'); + + await popupPO.pressShiftTab(); + await expect(popupPO.isActiveElement('field-2')).toBeTruthy('(10)'); + + await popupPO.pressShiftTab(); + await expect(popupPO.isActiveElement('field-1')).toBeTruthy('(11)'); + }); + + it('should autofocus the first element [testcase: 5782ab19-popup]', async () => { + await testingViewPO.navigateTo(); + const popupPanelPO = await testingViewPO.openPopupPanel(); + await popupPanelPO.enterQualifier({ + entity: 'testing', + testcase: '5782ab19-popup', + }); + await popupPanelPO.execute(); + await expectPopupToShow({symbolicAppName: 'testing-app', popupCssClass: 'e2e-popup-5782ab19', componentSelector: 'app-popup-5782ab19'}); + + const popupPO = new Testcase5782ab19PopupPO(); + await expect(popupPO.isActiveElement('field-1')).toBeTruthy('Expected first field to be the active element'); + }); + }); +}); diff --git a/projects/e2e/workbench-application-platform/test-runner/src/util/testing.util.ts b/projects/e2e/workbench-application-platform/test-runner/src/util/testing.util.ts new file mode 100644 index 000000000..0fc4d355f --- /dev/null +++ b/projects/e2e/workbench-application-platform/test-runner/src/util/testing.util.ts @@ -0,0 +1,147 @@ +import { $, browser, ElementFinder } from 'protractor'; + +/** + * Switches browser testing context to the main document. + */ +export async function switchToMainContext(): Promise { + await browser.switchTo().defaultContent(); + console.log(`Browser testing context switched: commands are sent to the main document.`); +} + +/** + * Switches browser testing context to given diff --git a/projects/scion/workbench/src/lib/remote-site/remote-site.component.scss b/projects/scion/workbench/src/lib/remote-site/remote-site.component.scss index 15ab81f54..6f7682156 100644 --- a/projects/scion/workbench/src/lib/remote-site/remote-site.component.scss +++ b/projects/scion/workbench/src/lib/remote-site/remote-site.component.scss @@ -4,5 +4,7 @@ > iframe { flex: auto; + min-width: 0; + min-height: 0; } } diff --git a/projects/scion/workbench/src/lib/remote-site/remote-site.component.ts b/projects/scion/workbench/src/lib/remote-site/remote-site.component.ts index 4c4e38fda..f89e0a054 100644 --- a/projects/scion/workbench/src/lib/remote-site/remote-site.component.ts +++ b/projects/scion/workbench/src/lib/remote-site/remote-site.component.ts @@ -60,6 +60,12 @@ export class RemoteSiteComponent implements OnDestroy { this._siteOrigin = new URL(url).origin; } + /** + * CSS classes to be added to the iframe tag. + */ + @Input() + public cssClass: string | string[]; + /** * Emits upon the receipt of a message from the remote site. * @@ -75,6 +81,30 @@ export class RemoteSiteComponent implements OnDestroy { @Output() public load = new EventEmitter(); + /** + * Emits when the remote site is about to lose focus. + * + * Requires the remote site to support this feature. + */ + @Output() + public synthFocusout = new EventEmitter(); + + /** + * Emits when the remote site is about to receive focus. + * + * Requires the remote site to support this feature. + */ + @Output() + public synthFocusin = new EventEmitter(); + + /** + * Emits when escape keystroke is pressed in the remote site. + * + * Requires the remote site to support this feature. + */ + @Output() + public synthEscape = new EventEmitter(); + constructor(private _sanitizer: DomSanitizer, private _workbenchLayout: WorkbenchLayoutService, private _renderer: Renderer2, @@ -125,7 +155,8 @@ export class RemoteSiteComponent implements OnDestroy { .subscribe(([event, iframe]: ['start' | 'end', HTMLIFrameElement]) => { if (event === 'start') { this._renderer.setStyle(iframe, 'pointer-events', 'none'); - } else { + } + else { this._renderer.removeStyle(iframe, 'pointer-events'); } }); @@ -147,6 +178,21 @@ export class RemoteSiteComponent implements OnDestroy { throw Error(`[OriginError] Message of illegal origin received [expected=${this._siteOrigin}, actual=${messageEvent.origin}]`); } + const synthEvent = parseSynthEvent(messageEvent.data); + if (synthEvent === 'sci-focusin') { + this._zone.run(() => this.synthFocusin.emit()); + return; + } + if (synthEvent === 'sci-focusout') { + this._zone.run(() => this.synthFocusout.emit()); + return; + } + if (synthEvent === 'sci-escape') { + this._zone.run(() => this.synthEscape.emit()); + document.dispatchEvent(new Event(synthEvent)); + return; + } + this.message.emit(messageEvent.data); // public API: do not emit inside Angular zone }); }); @@ -167,3 +213,24 @@ export class RemoteSiteComponent implements OnDestroy { function bufferUntil(closingNotifier$: Observable): OperatorFunction { return mergeMap((item: T) => combineLatest(of(item), closingNotifier$)); } + +/** + * Parses synthetic event sent by the remote site. + */ +function parseSynthEvent(data: any): string | null { + if (isNullOrUndefined(data) || typeof data !== 'object') { + return null; + } + if (data.protocol !== 'sci://workbench/remote-site') { + return null; + } + if (isNullOrUndefined(data.event)) { + return null; + } + + return data.event; +} + +function isNullOrUndefined(value: any): boolean { + return value === null || value === undefined; +} diff --git a/projects/scion/workbench/src/lib/routing/wb-router-link.directive.ts b/projects/scion/workbench/src/lib/routing/wb-router-link.directive.ts index 6db1b0ad3..b835a794f 100644 --- a/projects/scion/workbench/src/lib/routing/wb-router-link.directive.ts +++ b/projects/scion/workbench/src/lib/routing/wb-router-link.directive.ts @@ -19,6 +19,9 @@ import { WorkbenchService } from '../workbench.service'; /** * Like 'RouterLink' but with functionality to target a view outlet. * + * If in the context of a view and CTRL key is not pressed, by default, navigation replaces the content of the current view. + * Override this default behavior by setting a view target strategy in navigational extras. + * * By default, navigation is relative to the currently activated route, if any. * Prepend the path with a forward slash '/' to navigate absolutely, or set `relativeTo` property in navigational extras to `null`. */ diff --git a/projects/scion/workbench/src/lib/view-part/view-tab/view-tab.component.html b/projects/scion/workbench/src/lib/view-part/view-tab/view-tab.component.html index cb7ba7a92..4f31e9e98 100644 --- a/projects/scion/workbench/src/lib/view-part/view-tab/view-tab.component.html +++ b/projects/scion/workbench/src/lib/view-part/view-tab/view-tab.component.html @@ -1,13 +1,13 @@
-
- +
+ {{view.title}}
-
+
{{heading}}
diff --git a/projects/scion/workbench/src/lib/workbench.service.ts b/projects/scion/workbench/src/lib/workbench.service.ts index 8ba001170..be55251c3 100644 --- a/projects/scion/workbench/src/lib/workbench.service.ts +++ b/projects/scion/workbench/src/lib/workbench.service.ts @@ -17,7 +17,7 @@ import { Router } from '@angular/router'; /** * Root object for the SCION Workbench. * - * It consists of one or more viewparts containing views which can be flexible arranged and dragged around by the user. + * It consists of one or more viewparts containing views which can be flexibly arranged and dragged around by the user. * * The Workbench provides core features of a modern rich web application. * diff --git a/travis-scripts/cd.sh b/travis-scripts/cd.sh new file mode 100644 index 000000000..3a6d58308 --- /dev/null +++ b/travis-scripts/cd.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +TRAVIS_BUILD_DIR=$1 +DIST_DIR=$2 + +echo "Change directory: $DIST_DIR" +cd $TRAVIS_BUILD_DIR/$DIST_DIR diff --git a/travis-scripts/deploy-now.sh b/travis-scripts/deploy-now.sh new file mode 100644 index 000000000..ce0dfa609 --- /dev/null +++ b/travis-scripts/deploy-now.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +TRAVIS_BUILD_DIR=$1 +DIST_DIR=$2 +NOW_TOKEN=$3 + +echo "Change directory: $DIST_DIR" +cd $TRAVIS_BUILD_DIR/$DIST_DIR + +echo "Deploying '$DIST_DIR' to now 'https://zeit.co/scion'" +$TRAVIS_BUILD_DIR/node_modules/.bin/now deploy --token $NOW_TOKEN --team scion && $TRAVIS_BUILD_DIR/node_modules/.bin/now alias --token $NOW_TOKEN --team scion + +echo "Change directory: $TRAVIS_BUILD_DIR" +cd $TRAVIS_BUILD_DIR diff --git a/tsconfig.json b/tsconfig.json index 904c13b0c..298bb0495 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,30 @@ "@scion/workbench/*": [ "dist/scion/workbench/*" ], + "@scion/workbench-application-platform.api": [ + "dist/scion/workbench-application-platform.api" + ], + "@scion/workbench-application-platform.api/*": [ + "dist/scion/workbench-application-platform.api/*" + ], + "@scion/workbench-application-platform": [ + "dist/scion/workbench-application-platform" + ], + "@scion/workbench-application-platform/*": [ + "dist/scion/workbench-application-platform/*" + ], + "@scion/workbench-application.core": [ + "dist/scion/workbench-application.core" + ], + "@scion/workbench-application.core/*": [ + "dist/scion/workbench-application.core/*" + ], + "@scion/workbench-application.angular": [ + "dist/scion/workbench-application.angular" + ], + "@scion/workbench-application.angular/*": [ + "dist/scion/workbench-application.angular/*" + ], "@scion/mouse-dispatcher": [ "dist/scion/mouse-dispatcher" ], @@ -40,7 +64,73 @@ ], "@scion/viewport/*": [ "dist/scion/viewport/*" + ], + "@scion/e2e/common": [ + "dist/scion/e2e/common" + ], + "@scion/e2e/common/*": [ + "dist/scion/e2e/common/*" ] } +// /* +// * Path override to provide LIVE artifacts. +// * Note: DO NOT ACTIVATE FOR PRODUCTION! +// */ +// "paths": { +// "@scion/workbench": [ +// "projects/scion/workbench/src/public_api" +// ], +// "@scion/workbench/*": [ +// "projects/scion/workbench/src/*" +// ], +// "@scion/workbench-application-platform.api": [ +// "projects/scion/workbench-application-platform.api/src/public_api" +// ], +// "@scion/workbench-application-platform.api/*": [ +// "projects/scion/workbench-application-platform.api/src/*" +// ], +// "@scion/workbench-application-platform": [ +// "projects/scion/workbench-application-platform/src/public_api" +// ], +// "@scion/workbench-application-platform/*": [ +// "projects/scion/workbench-application-platform/src/*" +// ], +// "@scion/workbench-application.core": [ +// "projects/scion/workbench-application.core/src/public_api" +// ], +// "@scion/workbench-application.core/*": [ +// "projects/scion/workbench-application.core/src/*" +// ], +// "@scion/workbench-application.angular": [ +// "projects/scion/workbench-application.angular/src/public_api" +// ], +// "@scion/workbench-application.angular/*": [ +// "projects/scion/workbench-application.angular/src/*" +// ], +// "@scion/mouse-dispatcher": [ +// "projects/scion/mouse-dispatcher/src/public_api" +// ], +// "@scion/mouse-dispatcher/*": [ +// "projects/scion/mouse-dispatcher/src/*" +// ], +// "@scion/dimension": [ +// "projects/scion/dimension/src/public_api" +// ], +// "@scion/dimension/*": [ +// "projects/scion/dimension/src/*" +// ], +// "@scion/viewport": [ +// "projects/scion/viewport/src/public_api" +// ], +// "@scion/viewport/*": [ +// "projects/scion/viewport/src/*" +// ], +// "@scion/e2e/common": [ +// "projects/e2e/workbench-application-platform/common/src/public_api" +// ], +// "@scion/e2e/common/*": [ +// "projects/e2e/workbench-application-platform/common/src/*" +// ] +// } } }