mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-13 07:00:06 +00:00
Compare commits
2 Commits
v1.4.0
...
suggestion
| Author | SHA1 | Date | |
|---|---|---|---|
| 9598c11f42 | |||
| 334a849caa |
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -21,11 +21,7 @@ jobs:
|
|||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- name: Install Dependencies
|
- run: npm ci
|
||||||
run: npm ci
|
- run: npm run package:lib
|
||||||
- name: Check code style
|
|
||||||
run: npm run lint
|
|
||||||
- name: Build Package
|
|
||||||
run: npm run package:lib
|
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
|
|||||||
4
.github/workflows/npmpublish.yml
vendored
4
.github/workflows/npmpublish.yml
vendored
@@ -6,7 +6,7 @@ name: SASjs Build and Publish
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -16,8 +16,6 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Check code style
|
|
||||||
run: npm run lint
|
|
||||||
- name: Build Project
|
- name: Build Project
|
||||||
run: npm run build
|
run: npm run build
|
||||||
- name: Semantic Release
|
- name: Semantic Release
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"trailingComma": "none",
|
|
||||||
"tabWidth": 2,
|
|
||||||
"semi": false,
|
|
||||||
"singleQuote": true
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,82 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Contributions to SASjs are very welcome! When making a PR, test cases should be included.
|
Contributions to SASjs are very welcome! When making a PR, test cases should be included. To help in unit testing, be sure to run the following when making changes:
|
||||||
|
|
||||||
This repository contains a suite of tests built using [@sasjs/test-framework](https://github.com/sasjs/test-framework).
|
```
|
||||||
|
# the following creates a tarball in the build folder of SASjs
|
||||||
|
npm run-script package:lib
|
||||||
|
|
||||||
Detailed instructions for creating and running the tests can be found [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md).
|
# now go to your app and run:
|
||||||
|
npm install ../sasjs/build/<tarball filename>
|
||||||
|
```
|
||||||
|
|
||||||
If you'd like to test your changes in an app that uses the adapter, you can do so as follows:
|
Tests are run using cypress. Before running tests, you need to define the following backend services:
|
||||||
|
|
||||||
1. Run `npm run package:lib` from the root folder in this repository.
|
# SAS 9
|
||||||
This creates a tarball in the `/build` folder.
|
|
||||||
2. In your app's root folder, run `npm install <path/to/tarball>`.
|
```
|
||||||
This will install the changed version of the adapter in your app.
|
|
||||||
|
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/main/mc_all.sas?_=1";
|
||||||
|
%inc mc;
|
||||||
|
filename ft15f001 temp;
|
||||||
|
parmcards4;
|
||||||
|
%webout(OPEN)
|
||||||
|
%macro x();
|
||||||
|
%do i=1 %to &_webin_file_count; %webout(OBJ,&&_webin_name&i) %end;
|
||||||
|
%mend; %x()
|
||||||
|
%webout(CLOSE)
|
||||||
|
;;;;
|
||||||
|
%mm_createwebservice(path=/Public/app/common,name=sendObj)
|
||||||
|
parmcards4;
|
||||||
|
%webout(OPEN)
|
||||||
|
%macro x();
|
||||||
|
%do i=1 %to &_webin_file_count; %webout(ARR,&&_webin_name&i) %end;
|
||||||
|
%mend; %x()
|
||||||
|
%webout(CLOSE)
|
||||||
|
;;;;
|
||||||
|
%mm_createwebservice(path=/Public/app/common,name=sendArr)
|
||||||
|
```
|
||||||
|
|
||||||
|
# Viya
|
||||||
|
|
||||||
|
```
|
||||||
|
filename mc url "https://raw.githubusercontent.com/macropeople/macrocore/main/mc_all.sas";
|
||||||
|
%inc mc;
|
||||||
|
|
||||||
|
filename ft15f001 temp;
|
||||||
|
parmcards4;
|
||||||
|
%webout(OPEN)
|
||||||
|
%global sasjs_tables;
|
||||||
|
%let sasjs_tables=&sasjs_tables;
|
||||||
|
%put &=sasjs_tables;
|
||||||
|
%let sasjs_tables=&sasjs_tables;
|
||||||
|
%macro x();
|
||||||
|
%global sasjs_tables;
|
||||||
|
%do i=1 %to %sysfunc(countw(&sasjs_tables));
|
||||||
|
%let table=%scan(&sasjs_tables,&i);
|
||||||
|
%webout(OBJ,&table)
|
||||||
|
%end;
|
||||||
|
%mend;
|
||||||
|
%x()
|
||||||
|
%webout(CLOSE)
|
||||||
|
;;;;
|
||||||
|
%mv_createwebservice(path=/Public/app/common,name=sendObj)
|
||||||
|
filename ft15f001 temp;
|
||||||
|
parmcards4;
|
||||||
|
%webout(OPEN)
|
||||||
|
%global sasjs_tables;
|
||||||
|
%let sasjs_tables=&sasjs_tables;
|
||||||
|
%put &=sasjs_tables;
|
||||||
|
%macro x();
|
||||||
|
%do i=1 %to %sysfunc(countw(&sasjs_tables));
|
||||||
|
%let table=%scan(&sasjs_tables,&i);
|
||||||
|
%webout(ARR,&table)
|
||||||
|
%end;
|
||||||
|
%mend;
|
||||||
|
%x()
|
||||||
|
%webout(CLOSE)
|
||||||
|
;;;;
|
||||||
|
%mv_createwebservice(path=/Public/app/common,name=sendArr)
|
||||||
|
```
|
||||||
|
|
||||||
|
The above services will return anything you send. To run the tests simply launch `npm run cypress`.
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ SASjs is a open-source framework for building Web Apps on SAS® platforms. You c
|
|||||||
|
|
||||||
3 - Reference directly from the CDN - in which case click [here](https://www.jsdelivr.com/package/npm/@sasjs/adapter?tab=collection) and select "SRI" to get the script tag with the integrity hash.
|
3 - Reference directly from the CDN - in which case click [here](https://www.jsdelivr.com/package/npm/@sasjs/adapter?tab=collection) and select "SRI" to get the script tag with the integrity hash.
|
||||||
|
|
||||||
If you are short on time and just need to build an app quickly, then check out [this video](https://vimeo.com/393161794) and the [react-seed-app](https://github.com/sasjs/react-seed-app) which provides some boilerplate.
|
If you are short on time and just need to build an app quickly, then check out [this video](https://vimeo.com/393161794) and the [react-seed-app](https://github.com/macropeople/react-seed-app) which provides some boilerplate.
|
||||||
|
|
||||||
For more information on building web apps with SAS, check out [sasjs.io](https://sasjs.io)
|
For more information on building web apps with SAS, check out [sasjs.io](https://sasjs.io)
|
||||||
|
|
||||||
## None of this makes sense. How do I build an app with it?
|
## None of this makes sense. How do I build an app with it?
|
||||||
|
|
||||||
Ok ok. Deploy this [example.html](https://raw.githubusercontent.com/sasjs/adapter/master/example.html) file to your web server, and update `servertype` to `SAS9` or `SASVIYA` depending on your backend.
|
Ok ok. Deploy this [example.html](https://github.com/sasjs/adapter/blob/main/example.html) file to your web server, and update `servertype` to `SAS9` or `SASVIYA` depending on your backend.
|
||||||
|
|
||||||
The backend part can be deployed as follows:
|
The backend part can be deployed as follows:
|
||||||
|
|
||||||
@@ -43,6 +43,6 @@ You now have a simple web app with a backend service!
|
|||||||
|
|
||||||
# More resources
|
# More resources
|
||||||
|
|
||||||
For more information and examples specific to this adapter you can check out the [user guide](https://sasjs.io/sasjs-adapter/) or the [technical](http://adapter.sasjs.io/) documentation.
|
For more information specific to this adapter you can check out this [user guide](https://sasjs.io/sasjs/sasjs-adapter/) or the [technical](http://adapter.sasjs.io/) documentation.
|
||||||
|
|
||||||
For more information on building web apps in general, check out these [resources](https://sasjs.io/training/resources/) or contact the [author](https://www.linkedin.com/in/allanbowe/) directly.
|
For more information on building web apps in general, check out these [resources](https://sasjs.io/training/resources/) or contact the [author](https://www.linkedin.com/in/allanbowe/) directly.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
340
docs/index.html
340
docs/index.html
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
211
example.html
211
example.html
@@ -1,109 +1,114 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script src="https://cdn.jsdelivr.net/combine/npm/chart.js@2.9.3,npm/jquery@3.5.1,npm/@sasjs/adapter@1"></script>
|
<meta charset='utf-8' http-equiv='X-UA-Compatible' content='IE=edge' />
|
||||||
<script>
|
<link rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css' integrity='sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh' crossorigin='anonymous'>
|
||||||
var sasJs = new SASjs.default({
|
<script src='https://cdn.jsdelivr.net/combine/npm/chart.js@2.9.3,npm/jquery@3.5.1,npm/@sasjs/adapter@1'></script>
|
||||||
appLoc: "/Public/app/readme"
|
<script>
|
||||||
,serverType:"SAS9"
|
const sasJs = new SASjs.default({
|
||||||
,debug: false
|
appLoc: '/Products/demo/readme',
|
||||||
});
|
serverType:'SAS9',
|
||||||
function initSasJs() {
|
debug: 'false'
|
||||||
$('#loading-spinner').show()
|
})
|
||||||
// instantiate sasjs with options such as backend app location
|
|
||||||
// login (it's also possible to set an autologin when making requests)
|
const initSasJs = () => {
|
||||||
sasJs.logIn(
|
$('#loading-spinner').show()
|
||||||
$('#username')[0].value
|
|
||||||
,$('#password')[0].value
|
// instantiate sasJs with options such as backend app location
|
||||||
).then((response) => {
|
// login (it's also possible to set an auto login when making requests)
|
||||||
if (response.isLoggedIn === true) {
|
sasJs.logIn($('#username')[0].value, $('#password')[0].value)
|
||||||
$('#loading-spinner').hide()
|
.then((response) => {
|
||||||
$('.login').hide()
|
if (response.isLoggedIn === true) {
|
||||||
$('#getdata').show()
|
$('#loading-spinner').hide()
|
||||||
$('#cars').show()
|
$('.login').hide()
|
||||||
}
|
$('#getDataBtn').show()
|
||||||
})
|
$('#cars').show()
|
||||||
}
|
}
|
||||||
function getData(){
|
})
|
||||||
$('#loading-spinner').show()
|
|
||||||
$('#myChart').remove();
|
|
||||||
$('#chart-container').append('<canvas id="myChart" style="display: none;"></canvas>')
|
|
||||||
// make a request to a SAS service
|
|
||||||
var type = $("#cars")[0].options[$("#cars")[0].selectedIndex].value;
|
|
||||||
// request data from an endpoint under your appLoc
|
|
||||||
sasJs.request("/common/getdata", {
|
|
||||||
// send data as an array of objects - each object is one row
|
|
||||||
fromjs: [{ type: type }]
|
|
||||||
}).then((response) => {
|
|
||||||
$('#myChart').show();
|
|
||||||
var labels = []
|
|
||||||
var data = []
|
|
||||||
response.areas.map((d) => {
|
|
||||||
labels.push(d.MAKE);
|
|
||||||
data.push(d.AVPRICE);
|
|
||||||
})
|
|
||||||
$('#loading-spinner').hide()
|
|
||||||
initGraph(labels, data, type);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
function initGraph(labels, data, type){
|
|
||||||
var myCanvas = document.getElementById("myChart");
|
|
||||||
var ctx = myCanvas.getContext("2d");
|
|
||||||
var myChart = new Chart(ctx, {
|
|
||||||
type: 'bar',
|
|
||||||
data: {
|
|
||||||
labels: labels,
|
|
||||||
datasets: [{
|
|
||||||
label: "Average Invoice Price in USD for " + type + " Cars by Manufacturer",
|
|
||||||
data: data,
|
|
||||||
backgroundColor: "rgba(255,99,132,0.2)",
|
|
||||||
borderColor: "rgba(255,99,132,1)",
|
|
||||||
borderWidth: 1,
|
|
||||||
hoverBackgroundColor: "rgba(255,99,132,0.4)",
|
|
||||||
hoverBorderColor: "rgba(255,99,132,1)",
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
scales: {yAxes: [{ticks: {beginAtZero: true}}]}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
// make a request to a SAS service
|
||||||
</script>
|
const getData = () => {
|
||||||
<meta charset="utf-8" http-equiv="X-UA-Compatible" content="IE=edge" />
|
$('#loading-spinner').show()
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
$('#myChart').remove()
|
||||||
</head>
|
$('#chart-container').append("<canvas id='myChart' style='display: none'></canvas>")
|
||||||
<body>
|
|
||||||
<div class="container-fluid" style="text-align: center; margin-top: 10px;">
|
const type = $('#cars')[0].options[$('#cars')[0].selectedIndex].value
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-5 col-md-7 col-sm-10 mx-auto mx-auto">
|
// request data from an endpoint under your appLoc
|
||||||
<h1>Demo Seed App for <span class="code">SASjs</span></h1>
|
// send data as an array of objects - each object is one row
|
||||||
<div class="login" id="login-form">
|
sasJs.request('/common/getdata', {fromjs: [{ type: type }]})
|
||||||
<div class="form-group">
|
.then((response) => {
|
||||||
<input class="form-control" type="text" id="username" placeholder="Enter username" />
|
$('#myChart').show()
|
||||||
|
$('#loading-spinner').hide()
|
||||||
|
|
||||||
|
const labels = response.areas.map(area => area.MAKE)
|
||||||
|
const data = response.areas.map(area => area.AVPRICE)
|
||||||
|
|
||||||
|
initGraph(labels, data, type)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const initGraph = (labels, data, type) => {
|
||||||
|
const myCanvas = document.getElementById('myChart')
|
||||||
|
const ctx = myCanvas.getContext('2d')
|
||||||
|
|
||||||
|
const myChart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [{
|
||||||
|
label: `Average Invoice Price in USD for ${type} Cars by Manufacturer`,
|
||||||
|
data: data,
|
||||||
|
backgroundColor: 'rgba(255,99,132,0.2)',
|
||||||
|
borderColor: 'rgba(255,99,132,1)',
|
||||||
|
borderWidth: 1,
|
||||||
|
hoverBackgroundColor: 'rgba(255,99,132,0.4)',
|
||||||
|
hoverBorderColor: 'rgba(255,99,132,1)',
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {yAxes: [{ticks: {beginAtZero: true}}]}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class='container-fluid' style='text-align: center; margin-top: 10px'>
|
||||||
|
<div class='row'>
|
||||||
|
<div class='col-lg-5 col-md-7 col-sm-10 mx-auto mx-auto'>
|
||||||
|
<h1>Demo Seed App for <span class='code'>SASjs</span></h1>
|
||||||
|
<div class='login' id='login-form'>
|
||||||
|
<div class='form-group'>
|
||||||
|
<input class='form-control' type='text' id='username' placeholder='Enter username' />
|
||||||
|
</div>
|
||||||
|
<div class='form-group'>
|
||||||
|
<input class='form-control' type='password' id='password' placeholder='Enter password' />
|
||||||
|
</div>
|
||||||
|
<button id='login' onclick='initSasJs()' class='login btn btn-primary' style='margin-bottom: 5px'>Log In</button>
|
||||||
|
</div>
|
||||||
|
<select name='cars' id='cars' style='margin-bottom: 5px; display: none' class='form-control'>
|
||||||
|
<option value='Hybrid'>Hybrid</option>
|
||||||
|
<option value='SUV'>SUV</option>
|
||||||
|
<option value='Sedan'>Sedan</option>
|
||||||
|
<option value='Sports'>Sports</option>
|
||||||
|
<option value='Truck'>Truck</option>
|
||||||
|
<option value='Wagon'>Wagon</option>
|
||||||
|
</select>
|
||||||
|
<button id='getDataBtn' onclick='getData()' style='margin-bottom: 5px; display: none' class='btn btn-success'>Get Data</button>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<div id='loading-spinner' class='spinner-border text-primary' role='status' style='display: none'>
|
||||||
|
<span class='sr-only'>Loading...</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<input class="form-control" type="password" id="password" placeholder="Enter password" />
|
|
||||||
</div>
|
|
||||||
<button id="login" onclick="initSasJs()" class="login btn btn-primary" style="margin-bottom: 5px;">Log In</button>
|
|
||||||
</div>
|
|
||||||
<select name="cars" id="cars" style="margin-bottom: 5px; display: none;" class="form-control">
|
|
||||||
<option value="Hybrid">Hybrid</option>
|
|
||||||
<option value="SUV">SUV</option>
|
|
||||||
<option value="Sedan">Sedan</option>
|
|
||||||
<option value="Sports">Sports</option>
|
|
||||||
<option value="Truck">Truck</option>
|
|
||||||
<option value="Wagon">Wagon</option>
|
|
||||||
</select>
|
|
||||||
<button id="getdata" onclick="getData()" style="margin-bottom: 5px; display: none;" class="btn btn-success">Get Data</button><br><br>
|
|
||||||
<div id="loading-spinner" class="spinner-border text-primary" role="status" style="display: none;">
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div><br>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div id='chart-container' style='height: 65vh; width: 100%; position: relative; margin: auto'>
|
||||||
</div>
|
<canvas id='myChart' style='display: none'></canvas>
|
||||||
<div id="chart-container" style="height: 65vh; width: 100%; position: relative; margin: auto;">
|
</div>
|
||||||
<canvas id="myChart" style="display: none;"></canvas>
|
</body>
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</head>
|
</head>
|
||||||
1733
package-lock.json
generated
1733
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@@ -5,8 +5,8 @@
|
|||||||
"build": "rimraf build && webpack",
|
"build": "rimraf build && webpack",
|
||||||
"package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack",
|
"package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack",
|
||||||
"publish:lib": "npm run build && cd build && npm publish",
|
"publish:lib": "npm run build && cd build && npm publish",
|
||||||
"lint:fix": "npx prettier --write 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
|
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
|
||||||
"lint": "npx prettier --check 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
|
"lint": "tslint -p tsconfig.json",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
|
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
|
||||||
"postpublish": "git clean -fd",
|
"postpublish": "git clean -fd",
|
||||||
@@ -22,6 +22,9 @@
|
|||||||
{
|
{
|
||||||
"pkgRoot": "/build"
|
"pkgRoot": "/build"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"branches": [
|
||||||
|
"main"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -37,22 +40,23 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/isomorphic-fetch": "0.0.35",
|
"@types/isomorphic-fetch": "0.0.35",
|
||||||
"@types/jest": "^26.0.13",
|
"@types/jest": "^26.0.4",
|
||||||
"cp": "^0.2.0",
|
"cp": "^0.2.0",
|
||||||
"jest": "^25.5.4",
|
"jest": "^25.5.4",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
|
"prettier": "^2.0.5",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"semantic-release": "^17.1.1",
|
"semantic-release": "^17.1.1",
|
||||||
"ts-jest": "^25.5.1",
|
"ts-jest": "^25.5.1",
|
||||||
"ts-loader": "^8.0.3",
|
"ts-loader": "^7.0.5",
|
||||||
"tslint": "^6.1.3",
|
"tslint": "^6.1.2",
|
||||||
"tslint-config-prettier": "^1.18.0",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"typedoc": "^0.17.8",
|
"typedoc": "^0.17.8",
|
||||||
"typedoc-neo-theme": "^1.0.10",
|
"typedoc-neo-theme": "^1.0.9",
|
||||||
"typedoc-plugin-external-module-name": "^4.0.3",
|
"typedoc-plugin-external-module-name": "^4.0.3",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^3.9.6",
|
||||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||||
"webpack": "^4.44.1",
|
"webpack": "^4.43.0",
|
||||||
"webpack-cli": "^3.3.12"
|
"webpack-cli": "^3.3.12"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"trailingComma": "none",
|
|
||||||
"tabWidth": 2,
|
|
||||||
"semi": true,
|
|
||||||
"singleQuote": false
|
|
||||||
}
|
|
||||||
@@ -1,139 +1,68 @@
|
|||||||
`sasjs-tests` is a test suite for the SASjs adapter.
|
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||||
|
|
||||||
It is a React app bootstrapped using [Create React App](https://github.com/facebook/create-react-app) and [@sasjs/test-framework](https://github.com/sasjs/test-framework).
|
## Available Scripts
|
||||||
|
|
||||||
When developing on `@sasjs/adapter`, it's good practice to run the test suite against your changed version of the adapter to ensure that existing functionality has not been impacted.
|
In the project directory, you can run:
|
||||||
|
|
||||||
You can use the provided `update:adapter` NPM script for this.
|
### `npm start`
|
||||||
|
|
||||||
```
|
Runs the app in the development mode.<br />
|
||||||
npm run update:adapter
|
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||||
```
|
|
||||||
|
|
||||||
This scripts builds a new version of the adapter and installs it in the `sasjs-tests` project.
|
The page will reload if you make edits.<br />
|
||||||
|
You will also see any lint errors in the console.
|
||||||
|
|
||||||
## Running tests
|
### `npm test`
|
||||||
|
|
||||||
There are three prerequisites to be able to run the tests:
|
Launches the test runner in the interactive watch mode.<br />
|
||||||
|
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||||
|
|
||||||
1. Correct server configuration for the SASjs adapter.
|
### `npm run build`
|
||||||
2. `sasjs-tests` deployed to your SAS server.
|
|
||||||
3. The required SAS services created on the same server.
|
|
||||||
|
|
||||||
### 1. Configuring the SASjs adapter
|
Builds the app for production to the `build` folder.<br />
|
||||||
|
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||||
|
|
||||||
There is a `config.json` file in the `/public` folder which specifies the configuration for the SASjs adapter. You can set the values within the `sasjsConfig` property in this file to match your SAS server configuration.
|
The build is minified and the filenames include the hashes.<br />
|
||||||
|
Your app is ready to be deployed!
|
||||||
|
|
||||||
### 2. Deploying to your SAS server
|
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||||
|
|
||||||
There is a `deploy` NPM script provided in the `sasjs-tests` project's `package.json`.
|
### `npm run eject`
|
||||||
|
|
||||||
It updates `sasjs-tests` to use the latest version of the adapter, and deploys to a specified server via SSH using the `rsync` command.
|
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||||
|
|
||||||
To be able to run the `deploy` script, two environment variables need to be set:
|
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||||
|
|
||||||
- `SSH_ACCOUNT` - your SSH account, this is of the form username@domain.com
|
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||||
- `DEPLOY_PATH` - the path on the server where `sasjs-tests` will be deployed to, typically `/var/www/html/<some-subfolder>`.
|
|
||||||
|
|
||||||
So you can run the script like so:
|
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||||
|
|
||||||
```
|
## Learn More
|
||||||
SSH_ACCOUNT=me@my-sas-server.com DEPLOY_PATH=/var/www/html/my-folder/sasjs-tests npm run deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
If you'd like to deploy just `sasjs-tests` without changing the adapter version, you can use the `deploy:tests` script, while also setting the same environment variables as above.
|
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||||
|
|
||||||
## 3. Creating the required SAS services
|
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||||
|
|
||||||
The below services need to be created on your SAS server, at the location specified as the `appLoc` in the SASjs configuration.
|
### Code Splitting
|
||||||
|
|
||||||
### SAS 9
|
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
|
||||||
|
|
||||||
```
|
### Analyzing the Bundle Size
|
||||||
|
|
||||||
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
|
||||||
%inc mc;
|
|
||||||
filename ft15f001 temp;
|
|
||||||
parmcards4;
|
|
||||||
%webout(OPEN)
|
|
||||||
%macro x();
|
|
||||||
%do i=1 %to &_webin_file_count; %webout(OBJ,&&_webin_name&i) %end;
|
|
||||||
%mend; %x()
|
|
||||||
%webout(CLOSE)
|
|
||||||
;;;;
|
|
||||||
%mm_createwebservice(path=/Public/app/common,name=sendObj)
|
|
||||||
parmcards4;
|
|
||||||
%webout(OPEN)
|
|
||||||
%macro x();
|
|
||||||
%do i=1 %to &_webin_file_count; %webout(ARR,&&_webin_name&i) %end;
|
|
||||||
%mend; %x()
|
|
||||||
%webout(CLOSE)
|
|
||||||
;;;;
|
|
||||||
%mm_createwebservice(path=/Public/app/common,name=sendArr)
|
|
||||||
```
|
|
||||||
|
|
||||||
### SAS Viya
|
### Making a Progressive Web App
|
||||||
|
|
||||||
```
|
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
|
||||||
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
|
||||||
%inc mc;
|
|
||||||
filename ft15f001 temp;
|
|
||||||
parmcards4;
|
|
||||||
%webout(FETCH)
|
|
||||||
%webout(OPEN)
|
|
||||||
%macro x();
|
|
||||||
%do i=1 %to %sysfunc(countw(&sasjs_tables));
|
|
||||||
%let table=%scan(&sasjs_tables,&i);
|
|
||||||
%webout(OBJ,&table)
|
|
||||||
%end;
|
|
||||||
%mend;
|
|
||||||
%x()
|
|
||||||
%webout(CLOSE)
|
|
||||||
;;;;
|
|
||||||
%mp_createwebservice(path=/Public/app/common,name=sendObj)
|
|
||||||
filename ft15f001 temp;
|
|
||||||
parmcards4;
|
|
||||||
%webout(FETCH)
|
|
||||||
%webout(OPEN)
|
|
||||||
%macro x();
|
|
||||||
%do i=1 %to %sysfunc(countw(&sasjs_tables));
|
|
||||||
%let table=%scan(&sasjs_tables,&i);
|
|
||||||
%webout(ARR,&table)
|
|
||||||
%end;
|
|
||||||
%mend;
|
|
||||||
%x()
|
|
||||||
%webout(CLOSE)
|
|
||||||
;;;;
|
|
||||||
%mp_createwebservice(path=/Public/app/common,name=sendArr)
|
|
||||||
filename ft15f001 temp;
|
|
||||||
parmcards4;
|
|
||||||
If you can keep your head when all about you
|
|
||||||
Are losing theirs and blaming it on you,
|
|
||||||
If you can trust yourself when all men doubt you,
|
|
||||||
But make allowance for their doubting too;
|
|
||||||
;;;;
|
|
||||||
%mp_createwebservice(path=/Public/app/common,name=makeErr)
|
|
||||||
```
|
|
||||||
|
|
||||||
You should now be able to access the tests in your browser at the deployed path on your server.
|
### Advanced Configuration
|
||||||
|
|
||||||
## Creating new tests
|
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
|
||||||
|
|
||||||
The `src/testSuites` folder contains all the test suites currently available.
|
### Deployment
|
||||||
Each suite contains a set of specs, each of which looks like this:
|
|
||||||
|
|
||||||
```javascript
|
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
|
||||||
{
|
|
||||||
title: "Your test title",
|
|
||||||
description: "A slightly more detailed description",
|
|
||||||
test: async () => {
|
|
||||||
// typically makes a request using the adapter and returns a promise
|
|
||||||
},
|
|
||||||
assertion: (response: any) =>
|
|
||||||
// receives the response when the test promise resolves, runs an assertion and returns a boolean
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
A test suite is an array of such objects, along with a `name` property.
|
### `npm run build` fails to minify
|
||||||
|
|
||||||
You can add your test to one of the existing suites if suitable, or create a new file that specifies a new test suite.
|
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
|
||||||
|
|||||||
252
sasjs-tests/package-lock.json
generated
252
sasjs-tests/package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@sasjs/tests",
|
"name": "sasjs-tests",
|
||||||
"version": "1.0.0",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1356,81 +1356,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
|
||||||
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
|
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
|
||||||
},
|
},
|
||||||
"@sasjs/adapter": {
|
|
||||||
"version": "1.3.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sasjs/adapter/-/adapter-1.3.13.tgz",
|
|
||||||
"integrity": "sha512-dWcDxgY3FB7Yx1I5dPpeQeyJDu4lezhIFrjn6lbdwRhV15aqOt4l9o9qZP+VbgOXqyi9gN0Y+p+vs2chBDFQqg==",
|
|
||||||
"requires": {
|
|
||||||
"es6-promise": "^4.2.8",
|
|
||||||
"form-data": "^3.0.0",
|
|
||||||
"isomorphic-fetch": "^2.2.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"form-data": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==",
|
|
||||||
"requires": {
|
|
||||||
"asynckit": "^0.4.0",
|
|
||||||
"combined-stream": "^1.0.8",
|
|
||||||
"mime-types": "^2.1.12"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@sasjs/test-framework": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sasjs/test-framework/-/test-framework-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-Pd8PUH5B5RO6q4w3OQXX7aWicvA/CJMXA/FCf2xp332ZTKBb/5uV+HphAOFKpCh58y+ykYYVSV0ZaDO/4t1h3A==",
|
|
||||||
"requires": {
|
|
||||||
"@types/react-highlight.js": "^1.0.0",
|
|
||||||
"immer": "^7.0.7",
|
|
||||||
"moment": "^2.27.0",
|
|
||||||
"react-highlight.js": "^1.0.7",
|
|
||||||
"semantic-ui-css": "^2.4.1",
|
|
||||||
"semantic-ui-react": "^1.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"immer": {
|
|
||||||
"version": "7.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/immer/-/immer-7.0.7.tgz",
|
|
||||||
"integrity": "sha512-Q8yYwVADJXrNfp1ZUAh4XDHkcoE3wpdpb4mC5abDSajs2EbW8+cGdPyAnglMyLnm7EF6ojD2xBFX7L5i4TIytw=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@semantic-ui-react/event-stack": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@semantic-ui-react/event-stack/-/event-stack-3.1.1.tgz",
|
|
||||||
"integrity": "sha512-SA7VOu/tY3OkooR++mm9voeQrJpYXjJaMHO1aFCcSouS2xhqMR9Gnz0LEGLOR0h9ueWPBKaQzKIrx3FTTJZmUQ==",
|
|
||||||
"requires": {
|
|
||||||
"exenv": "^1.2.2",
|
|
||||||
"prop-types": "^15.6.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@sheerun/mutationobserver-shim": {
|
"@sheerun/mutationobserver-shim": {
|
||||||
"version": "0.3.3",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz",
|
||||||
"integrity": "sha512-DetpxZw1fzPD5xUBrIAoplLChO2VB8DlL5Gg+I1IR9b2wPqYIca2WSUxL5g1vLeR4MsQq1NeWriXAVffV+U1Fw=="
|
"integrity": "sha512-DetpxZw1fzPD5xUBrIAoplLChO2VB8DlL5Gg+I1IR9b2wPqYIca2WSUxL5g1vLeR4MsQq1NeWriXAVffV+U1Fw=="
|
||||||
},
|
},
|
||||||
"@stardust-ui/react-component-event-listener": {
|
|
||||||
"version": "0.38.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@stardust-ui/react-component-event-listener/-/react-component-event-listener-0.38.0.tgz",
|
|
||||||
"integrity": "sha512-sIP/e0dyOrrlb8K7KWumfMxj/gAifswTBC4o68Aa+C/GA73ccRp/6W1VlHvF/dlOR4KLsA+5SKnhjH36xzPsWg==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.1.2",
|
|
||||||
"prop-types": "^15.7.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@stardust-ui/react-component-ref": {
|
|
||||||
"version": "0.38.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@stardust-ui/react-component-ref/-/react-component-ref-0.38.0.tgz",
|
|
||||||
"integrity": "sha512-xjs6WnvJVueSIXMWw0C3oWIgAPpcD03qw43oGOjUXqFktvpNkB73JoKIhS4sCrtQxBdct75qqr4ZL6JiyPcESw==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.1.2",
|
|
||||||
"prop-types": "^15.7.2",
|
|
||||||
"react-is": "^16.6.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@svgr/babel-plugin-add-jsx-attribute": {
|
"@svgr/babel-plugin-add-jsx-attribute": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz",
|
||||||
@@ -1906,14 +1836,6 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/react-highlight.js": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-highlight.js/-/react-highlight.js-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-5VXEuo2O9L66y/2GDQSGFTggQkpOvDc/p2ma1KHadu7o/H720HK3Fr83epd4wtQky7B/RoCPat0SKyhlhiUo7A==",
|
|
||||||
"requires": {
|
|
||||||
"@types/react": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/react-router": {
|
"@types/react-router": {
|
||||||
"version": "5.1.8",
|
"version": "5.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.8.tgz",
|
||||||
@@ -3832,11 +3754,6 @@
|
|||||||
"shallow-clone": "^0.1.2"
|
"shallow-clone": "^0.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"clsx": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="
|
|
||||||
},
|
|
||||||
"co": {
|
"co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
@@ -4178,15 +4095,6 @@
|
|||||||
"sha.js": "^2.4.8"
|
"sha.js": "^2.4.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"create-react-context": {
|
|
||||||
"version": "0.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz",
|
|
||||||
"integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==",
|
|
||||||
"requires": {
|
|
||||||
"gud": "^1.0.0",
|
|
||||||
"warning": "^4.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cross-spawn": {
|
"cross-spawn": {
|
||||||
"version": "6.0.5",
|
"version": "6.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||||
@@ -5004,21 +4912,11 @@
|
|||||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||||
},
|
},
|
||||||
"encoding": {
|
"encoding": {
|
||||||
"version": "0.1.13",
|
"version": "0.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
|
||||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"iconv-lite": "^0.6.2"
|
"iconv-lite": "~0.4.13"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"iconv-lite": {
|
|
||||||
"version": "0.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
|
||||||
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
|
||||||
"requires": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"end-of-stream": {
|
"end-of-stream": {
|
||||||
@@ -5711,11 +5609,6 @@
|
|||||||
"strip-eof": "^1.0.0"
|
"strip-eof": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exenv": {
|
|
||||||
"version": "1.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
|
|
||||||
"integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
|
|
||||||
},
|
|
||||||
"exit": {
|
"exit": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
|
||||||
@@ -6585,11 +6478,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
|
||||||
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE="
|
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE="
|
||||||
},
|
},
|
||||||
"gud": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw=="
|
|
||||||
},
|
|
||||||
"gzip-size": {
|
"gzip-size": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz",
|
||||||
@@ -6734,11 +6622,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
|
||||||
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
|
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
|
||||||
},
|
},
|
||||||
"highlight.js": {
|
|
||||||
"version": "9.18.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.3.tgz",
|
|
||||||
"integrity": "sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ=="
|
|
||||||
},
|
|
||||||
"history": {
|
"history": {
|
||||||
"version": "4.10.1",
|
"version": "4.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
|
||||||
@@ -8091,11 +7974,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jquery": {
|
|
||||||
"version": "3.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
|
|
||||||
"integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
|
|
||||||
},
|
|
||||||
"js-base64": {
|
"js-base64": {
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.2.tgz",
|
||||||
@@ -8245,11 +8123,6 @@
|
|||||||
"object.assign": "^4.1.0"
|
"object.assign": "^4.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keyboard-key": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ=="
|
|
||||||
},
|
|
||||||
"killable": {
|
"killable": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
|
||||||
@@ -9014,11 +8887,6 @@
|
|||||||
"minimist": "^1.2.5"
|
"minimist": "^1.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"moment": {
|
|
||||||
"version": "2.27.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
|
|
||||||
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
|
|
||||||
},
|
|
||||||
"move-concurrently": {
|
"move-concurrently": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||||
@@ -10012,11 +9880,6 @@
|
|||||||
"ts-pnp": "^1.1.6"
|
"ts-pnp": "^1.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"popper.js": {
|
|
||||||
"version": "1.16.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
|
||||||
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
|
|
||||||
},
|
|
||||||
"portfinder": {
|
"portfinder": {
|
||||||
"version": "1.0.26",
|
"version": "1.0.26",
|
||||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
|
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
|
||||||
@@ -11458,34 +11321,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
|
||||||
"integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA=="
|
"integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA=="
|
||||||
},
|
},
|
||||||
"react-highlight.js": {
|
|
||||||
"version": "1.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-highlight.js/-/react-highlight.js-1.0.7.tgz",
|
|
||||||
"integrity": "sha512-OVPKnV0ZvU+V//HExwbV8M9CWy49Eo/9y9pBN2OsNWUFPN6dE4YZBLmJW/5sM2DxI5v/QQLyxOnTnSSfGCP+9Q==",
|
|
||||||
"requires": {
|
|
||||||
"highlight.js": "^9.3.0",
|
|
||||||
"prop-types": "^15.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-is": {
|
"react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
},
|
},
|
||||||
"react-popper": {
|
|
||||||
"version": "1.3.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz",
|
|
||||||
"integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.1.2",
|
|
||||||
"create-react-context": "^0.3.0",
|
|
||||||
"deep-equal": "^1.1.1",
|
|
||||||
"popper.js": "^1.14.4",
|
|
||||||
"prop-types": "^15.6.1",
|
|
||||||
"typed-styles": "^0.0.7",
|
|
||||||
"warning": "^4.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-router": {
|
"react-router": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
|
||||||
@@ -12110,6 +11950,27 @@
|
|||||||
"resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-10.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-10.0.0.tgz",
|
||||||
"integrity": "sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg=="
|
"integrity": "sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg=="
|
||||||
},
|
},
|
||||||
|
"sasjs": {
|
||||||
|
"version": "file:../build/sasjs-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-8Ez2iS8BKzu2GG1Cwf/pe5PgNvdhowFodQNCTHIxMlDYgLqmg1mcpwRjJjnXF9A73gX0NkR65olYYAesp8cMMA==",
|
||||||
|
"requires": {
|
||||||
|
"es6-promise": "^4.2.8",
|
||||||
|
"form-data": "^3.0.0",
|
||||||
|
"isomorphic-fetch": "^2.2.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"form-data": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==",
|
||||||
|
"requires": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"sass-graph": {
|
"sass-graph": {
|
||||||
"version": "2.2.5",
|
"version": "2.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz",
|
||||||
@@ -12225,47 +12086,6 @@
|
|||||||
"node-forge": "0.9.0"
|
"node-forge": "0.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"semantic-ui-css": {
|
|
||||||
"version": "2.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/semantic-ui-css/-/semantic-ui-css-2.4.1.tgz",
|
|
||||||
"integrity": "sha512-Pkp0p9oWOxlH0kODx7qFpIRYpK1T4WJOO4lNnpNPOoWKCrYsfHqYSKgk5fHfQtnWnsAKy7nLJMW02bgDWWFZFg==",
|
|
||||||
"requires": {
|
|
||||||
"jquery": "x.*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"semantic-ui-react": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/semantic-ui-react/-/semantic-ui-react-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-9tNL94nEy16RdupTQNiURyemWUIxtTpQgFimCbOOHRBOe1ApsFz3FWFsrGjv9zFtE7dQMslLYov9BQOelTCVwA==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.10.5",
|
|
||||||
"@semantic-ui-react/event-stack": "^3.1.0",
|
|
||||||
"@stardust-ui/react-component-event-listener": "~0.38.0",
|
|
||||||
"@stardust-ui/react-component-ref": "~0.38.0",
|
|
||||||
"clsx": "^1.1.1",
|
|
||||||
"keyboard-key": "^1.1.0",
|
|
||||||
"lodash": "^4.17.19",
|
|
||||||
"prop-types": "^15.7.2",
|
|
||||||
"react-is": "^16.8.6",
|
|
||||||
"react-popper": "^1.3.7",
|
|
||||||
"shallowequal": "^1.1.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": {
|
|
||||||
"version": "7.11.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
|
|
||||||
"integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
|
|
||||||
"requires": {
|
|
||||||
"regenerator-runtime": "^0.13.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lodash": {
|
|
||||||
"version": "4.17.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
|
||||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "6.3.0",
|
"version": "6.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||||
@@ -12455,11 +12275,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"shallowequal": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
|
||||||
},
|
|
||||||
"shebang-command": {
|
"shebang-command": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||||
@@ -13628,11 +13443,6 @@
|
|||||||
"mime-types": "~2.1.24"
|
"mime-types": "~2.1.24"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typed-styles": {
|
|
||||||
"version": "0.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz",
|
|
||||||
"integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q=="
|
|
||||||
},
|
|
||||||
"typedarray": {
|
"typedarray": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
@@ -13934,14 +13744,6 @@
|
|||||||
"makeerror": "1.0.x"
|
"makeerror": "1.0.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"warning": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
|
||||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
|
||||||
"requires": {
|
|
||||||
"loose-envify": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"watchpack": {
|
"watchpack": {
|
||||||
"version": "1.7.2",
|
"version": "1.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz",
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@sasjs/tests",
|
"name": "sasjs-tests",
|
||||||
"version": "1.0.0",
|
"version": "0.1.0",
|
||||||
"homepage": ".",
|
"homepage": ".",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/adapter": "^1.3.13",
|
|
||||||
"@sasjs/test-framework": "^1.4.0",
|
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.5.0",
|
"@testing-library/react": "^9.5.0",
|
||||||
"@testing-library/user-event": "^7.2.1",
|
"@testing-library/user-event": "^7.2.1",
|
||||||
@@ -18,6 +16,7 @@
|
|||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "3.4.1",
|
"react-scripts": "3.4.1",
|
||||||
|
"sasjs": "file:../build/sasjs-5.0.0.tgz",
|
||||||
"typescript": "^3.9.6"
|
"typescript": "^3.9.6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -25,9 +24,7 @@
|
|||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz",
|
"deploy": "rsync -avhe ssh ./build/* --delete kriaco@sas.analytium.co.uk:/var/www/html/kriaco/sasjs-tests"
|
||||||
"deploy:tests": "npm run build && rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH",
|
|
||||||
"deploy": "npm run update:adapter && npm run deploy:tests"
|
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": "react-app"
|
"extends": "react-app"
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
"appLoc": "/Public/app",
|
"appLoc": "/Public/app",
|
||||||
"serverType": "SASVIYA",
|
"serverType": "SASVIYA",
|
||||||
"debug": false,
|
"debug": false,
|
||||||
"contextName": "SharedCompute",
|
"contextName": null
|
||||||
"useComputeApi": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
102
sasjs-tests/src/App.scss
Normal file
102
sasjs-tests/src/App.scss
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
.app {
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.debug-toggle,
|
||||||
|
.app-loc-input,
|
||||||
|
.submit-button {
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
margin: 16px;
|
||||||
|
|
||||||
|
&.app-loc {
|
||||||
|
width: 20vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button {
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-loc-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
padding: 0 8px;
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$height: 40px;
|
||||||
|
$width: 70px;
|
||||||
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
width: $width;
|
||||||
|
height: $height;
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
input:checked + .knob {
|
||||||
|
animation: colorChange 0.4s linear forwards;
|
||||||
|
}
|
||||||
|
input:checked + .knob:before {
|
||||||
|
animation: turnON 0.4s linear forwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes colorChange {
|
||||||
|
from {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-color: #a4d9ad;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
background-color: #4bd663;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes turnON {
|
||||||
|
from {
|
||||||
|
transform: translateX(0px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX($width - ($height * 0.99));
|
||||||
|
box-shadow: -10px 0px 44px 0px #434343;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.knob {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
border-radius: $height;
|
||||||
|
}
|
||||||
|
|
||||||
|
.knob:before {
|
||||||
|
position: absolute;
|
||||||
|
background-color: white;
|
||||||
|
content: "";
|
||||||
|
left: $height * 0.1;
|
||||||
|
top: $height * 0.1;
|
||||||
|
width: ($height * 0.8);
|
||||||
|
height: ($height * 0.8);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { render } from "@testing-library/react";
|
import { render } from '@testing-library/react';
|
||||||
import App from "./App";
|
import App from './App';
|
||||||
|
|
||||||
test("renders learn react link", () => {
|
test('renders learn react link', () => {
|
||||||
const { getByText } = render(<App />);
|
const { getByText } = render(<App />);
|
||||||
const linkElement = getByText(/learn react/i);
|
const linkElement = getByText(/learn react/i);
|
||||||
|
|
||||||
expect(linkElement).toBeInTheDocument();
|
expect(linkElement).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,30 +1,54 @@
|
|||||||
import React, { ReactElement, useState, useContext, useEffect } from "react";
|
import React, { ReactElement, useState, useContext, useEffect } from "react";
|
||||||
import { TestSuiteRunner, TestSuite, AppContext } from "@sasjs/test-framework";
|
import "./App.scss";
|
||||||
import { basicTests } from "./testSuites/Basic";
|
import TestSuiteRunner from "./TestSuiteRunner";
|
||||||
import { sendArrTests, sendObjTests } from "./testSuites/RequestData";
|
import { AppContext } from "./context/AppContext";
|
||||||
import { specialCaseTests } from "./testSuites/SpecialCases";
|
|
||||||
import { sasjsRequestTests } from "./testSuites/SasjsRequests";
|
|
||||||
import "@sasjs/test-framework/dist/index.css";
|
|
||||||
|
|
||||||
const App = (): ReactElement<{}> => {
|
const App = (): ReactElement<{}> => {
|
||||||
const { adapter, config } = useContext(AppContext);
|
const [appLoc, setAppLoc] = useState("");
|
||||||
const [testSuites, setTestSuites] = useState<TestSuite[]>([]);
|
const [debug, setDebug] = useState(false);
|
||||||
|
const { adapter } = useContext(AppContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (adapter) {
|
if (adapter) adapter.setDebugState(debug);
|
||||||
setTestSuites([
|
}, [debug, adapter]);
|
||||||
basicTests(adapter, config.userName, config.password),
|
|
||||||
sendArrTests(adapter),
|
useEffect(() => {
|
||||||
sendObjTests(adapter),
|
if (appLoc && adapter) {
|
||||||
specialCaseTests(adapter),
|
adapter.setSASjsConfig({ ...adapter.getSasjsConfig(), appLoc });
|
||||||
sasjsRequestTests(adapter)
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}, [adapter, config]);
|
}, [appLoc, adapter]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAppLoc(adapter.getSasjsConfig().appLoc);
|
||||||
|
}, [adapter]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
{adapter && testSuites && <TestSuiteRunner testSuites={testSuites} />}
|
<div className="controls">
|
||||||
|
<div className="row">
|
||||||
|
<label>Debug</label>
|
||||||
|
<div className="debug-toggle">
|
||||||
|
<label className="switch">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
onChange={(e) => setDebug(e.target.checked)} // FIXME: rename 'e' => 'event'
|
||||||
|
/>
|
||||||
|
<span className="knob"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="row app-loc">
|
||||||
|
<label>App Loc</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="app-loc-input"
|
||||||
|
value={appLoc}
|
||||||
|
onChange={(e) => setAppLoc(e.target.value)} // FIXME: rename 'e' => 'event'
|
||||||
|
placeholder="AppLoc"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{adapter && <TestSuiteRunner adapter={adapter} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement, useState, useCallback, useContext } from "react";
|
import React, { ReactElement, useState, useCallback, useContext } from "react";
|
||||||
import "./Login.scss";
|
import "./Login.scss";
|
||||||
import { AppContext } from "@sasjs/test-framework";
|
import { AppContext } from "./context/AppContext";
|
||||||
import { Redirect } from "react-router-dom";
|
import { Redirect } from "react-router-dom";
|
||||||
|
|
||||||
const Login = (): ReactElement<{}> => {
|
const Login = (): ReactElement<{}> => {
|
||||||
@@ -9,11 +9,14 @@ const Login = (): ReactElement<{}> => {
|
|||||||
const appContext = useContext(AppContext);
|
const appContext = useContext(AppContext);
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(e) => {
|
(e) => { // FIXME: rename 'e' => 'event'
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
appContext.adapter.logIn(username, password).then((res) => {
|
|
||||||
appContext.setIsLoggedIn(res.isLoggedIn);
|
appContext.adapter.logIn(username, password)
|
||||||
});
|
.then(() => {
|
||||||
|
appContext.setIsLoggedIn(true);
|
||||||
|
});
|
||||||
|
// FIXME: catch block
|
||||||
},
|
},
|
||||||
[username, password, appContext]
|
[username, password, appContext]
|
||||||
);
|
);
|
||||||
@@ -38,7 +41,7 @@ const Login = (): ReactElement<{}> => {
|
|||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
required
|
required
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)} // FIXME: rename 'e' => 'event'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" className="submit-button">
|
<button type="submit" className="submit-button">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement, useContext, FunctionComponent } from "react";
|
import React, { ReactElement, useContext, FunctionComponent } from "react";
|
||||||
import { Redirect, Route } from "react-router-dom";
|
import { Redirect, Route } from "react-router-dom";
|
||||||
import { AppContext } from "@sasjs/test-framework";
|
import { AppContext } from "./context/AppContext";
|
||||||
|
|
||||||
interface PrivateRouteProps {
|
interface PrivateRouteProps {
|
||||||
component: FunctionComponent;
|
component: FunctionComponent;
|
||||||
@@ -8,16 +8,14 @@ interface PrivateRouteProps {
|
|||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PrivateRoute = (
|
const PrivateRoute = (props: PrivateRouteProps): ReactElement<PrivateRouteProps> => {
|
||||||
props: PrivateRouteProps
|
|
||||||
): ReactElement<PrivateRouteProps> => {
|
|
||||||
const { component, path, exact } = props;
|
const { component, path, exact } = props;
|
||||||
const appContext = useContext(AppContext);
|
const appContext = useContext(AppContext);
|
||||||
return appContext.isLoggedIn ? (
|
|
||||||
|
return appContext.isLoggedIn ?
|
||||||
<Route component={component} path={path} exact={exact} />
|
<Route component={component} path={path} exact={exact} />
|
||||||
) : (
|
:
|
||||||
<Redirect to="/login" />
|
<Redirect to="/login" />
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PrivateRoute;
|
export default PrivateRoute;
|
||||||
19
sasjs-tests/src/TestSuiteRunner.scss
Normal file
19
sasjs-tests/src/TestSuiteRunner.scss
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
.button-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button {
|
||||||
|
padding: 10px;
|
||||||
|
min-height: 80px;
|
||||||
|
font-size: 2em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
125
sasjs-tests/src/TestSuiteRunner.tsx
Normal file
125
sasjs-tests/src/TestSuiteRunner.tsx
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import React, { useEffect, useState, ReactElement, useContext } from "react";
|
||||||
|
import TestSuiteComponent from "./components/TestSuite";
|
||||||
|
import TestSuiteCard from "./components/TestSuiteCard";
|
||||||
|
import { TestSuite, Test } from "./types";
|
||||||
|
import { basicTests } from "./testSuites/Basic"; // FIXME: declared but never used
|
||||||
|
import "./TestSuiteRunner.scss";
|
||||||
|
import SASjs from "sasjs";
|
||||||
|
import { AppContext } from "./context/AppContext";
|
||||||
|
import { sendArrTests, sendObjTests } from "./testSuites/RequestData"; // FIXME: declared but never used
|
||||||
|
import { specialCaseTests } from "./testSuites/SpecialCases";
|
||||||
|
import { sasjsRequestTests } from "./testSuites/SasjsRequests"; // FIXME: declared but never used
|
||||||
|
|
||||||
|
interface TestSuiteRunnerProps {
|
||||||
|
adapter: SASjs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TestSuiteRunner = (props: TestSuiteRunnerProps): ReactElement<TestSuiteRunnerProps> => {
|
||||||
|
const { adapter } = props;
|
||||||
|
const { config } = useContext(AppContext); // FIXME: declared but never used
|
||||||
|
const [testSuites, setTestSuites] = useState<TestSuite[]>([]);
|
||||||
|
const [runTests, setRunTests] = useState(false);
|
||||||
|
const [completedTestSuites, setCompletedTestSuites] = useState< // FIXME: create interface
|
||||||
|
{
|
||||||
|
name: string;
|
||||||
|
completedTests: {
|
||||||
|
test: Test;
|
||||||
|
result: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
executionTime: number;
|
||||||
|
}[];
|
||||||
|
}[]
|
||||||
|
>([]);
|
||||||
|
const [currentTestSuite, setCurrentTestSuite] = useState<TestSuite | null>(
|
||||||
|
(null as unknown) as TestSuite
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (adapter) {
|
||||||
|
setTestSuites([
|
||||||
|
// basicTests(adapter, config.userName, config.password),
|
||||||
|
// sendArrTests(adapter),
|
||||||
|
// sendObjTests(adapter),
|
||||||
|
specialCaseTests(adapter),
|
||||||
|
// sasjsRequestTests(adapter),
|
||||||
|
]);
|
||||||
|
setCompletedTestSuites([]);
|
||||||
|
}
|
||||||
|
}, [adapter]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (testSuites.length) {
|
||||||
|
setCurrentTestSuite(testSuites[0]);
|
||||||
|
}
|
||||||
|
}, [testSuites]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (runTests) {
|
||||||
|
setCompletedTestSuites([]);
|
||||||
|
setCurrentTestSuite(testSuites[0]);
|
||||||
|
}
|
||||||
|
}, [runTests, testSuites]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="button-container">
|
||||||
|
<button
|
||||||
|
className={runTests ? "submit-button disabled" : "submit-button"} // TODO: 'submit-button' class should be assigned by default
|
||||||
|
onClick={() => setRunTests(true)}
|
||||||
|
disabled={runTests}
|
||||||
|
>
|
||||||
|
{runTests ? (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
// FIXME: fragment is not needed in this case
|
||||||
|
}
|
||||||
|
<div className="loading-spinner"></div>Running tests...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Run tests!"
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{completedTestSuites.map((completedTestSuite, index) => { // TODO: refactor
|
||||||
|
return (
|
||||||
|
<TestSuiteCard
|
||||||
|
key={index}
|
||||||
|
tests={completedTestSuite.completedTests}
|
||||||
|
name={completedTestSuite.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{currentTestSuite && runTests && (
|
||||||
|
<TestSuiteComponent
|
||||||
|
{...currentTestSuite}
|
||||||
|
onCompleted={(
|
||||||
|
name,
|
||||||
|
completedTests: {
|
||||||
|
test: Test;
|
||||||
|
result: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
executionTime: number;
|
||||||
|
}[]
|
||||||
|
) => {
|
||||||
|
const currentIndex = testSuites.indexOf(currentTestSuite);
|
||||||
|
const nextIndex = currentIndex < testSuites.length - 1 ? currentIndex + 1 : -1;
|
||||||
|
|
||||||
|
if (nextIndex >= 0) setCurrentTestSuite(testSuites[nextIndex]);
|
||||||
|
else setCurrentTestSuite(null);
|
||||||
|
|
||||||
|
const newCompletedTestSuites = [
|
||||||
|
...completedTestSuites,
|
||||||
|
{ name, completedTests },
|
||||||
|
];
|
||||||
|
|
||||||
|
setCompletedTestSuites(newCompletedTestSuites);
|
||||||
|
|
||||||
|
if (newCompletedTestSuites.length === testSuites.length) setRunTests(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TestSuiteRunner;
|
||||||
87
sasjs-tests/src/components/Test.tsx
Normal file
87
sasjs-tests/src/components/Test.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import React, { ReactElement, useEffect, useState } from "react";
|
||||||
|
import TestCard from "./TestCard";
|
||||||
|
import { start } from "repl"; // FIXME: declared but never used
|
||||||
|
|
||||||
|
interface TestProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
beforeTest?: (...args: any) => Promise<any>;
|
||||||
|
afterTest?: (...args: any) => Promise<any>;
|
||||||
|
test: (context: any) => Promise<any>;
|
||||||
|
assertion: (...args: any) => boolean;
|
||||||
|
onCompleted: (payload: {
|
||||||
|
result: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
executionTime: number;
|
||||||
|
}) => void;
|
||||||
|
context: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatus = (isRunning: boolean, isPassed: boolean): string => {
|
||||||
|
return isRunning ? "running" : isPassed ? "passed" : "failed";
|
||||||
|
};
|
||||||
|
|
||||||
|
const Test = (props: TestProps): ReactElement<TestProps> => {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
test,
|
||||||
|
beforeTest,
|
||||||
|
afterTest,
|
||||||
|
assertion,
|
||||||
|
onCompleted,
|
||||||
|
context,
|
||||||
|
} = props;
|
||||||
|
const beforeTestFunction = beforeTest ? beforeTest : () => Promise.resolve();
|
||||||
|
const afterTestFunction = afterTest ? afterTest : () => Promise.resolve();
|
||||||
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
|
const [isPassed, setIsPassed] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (test && assertion) {
|
||||||
|
const startTime = new Date().valueOf()
|
||||||
|
|
||||||
|
setIsRunning(true);
|
||||||
|
setIsPassed(false);
|
||||||
|
|
||||||
|
beforeTestFunction()
|
||||||
|
.then(() => test(context))
|
||||||
|
.then((res) => {
|
||||||
|
setIsRunning(false);
|
||||||
|
setIsPassed(assertion(res, context))
|
||||||
|
|
||||||
|
return Promise.resolve(assertion(res, context));
|
||||||
|
})
|
||||||
|
.then((testResult) => {
|
||||||
|
afterTestFunction();
|
||||||
|
|
||||||
|
const endTime = new Date().valueOf();
|
||||||
|
const executionTime = (endTime - startTime) / 1000;
|
||||||
|
|
||||||
|
onCompleted({ result: testResult, error: null, executionTime });
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
setIsRunning(false);
|
||||||
|
setIsPassed(false);
|
||||||
|
|
||||||
|
console.error(e);
|
||||||
|
|
||||||
|
const endTime = new Date().valueOf();
|
||||||
|
const executionTime = (endTime - startTime) / 1000;
|
||||||
|
|
||||||
|
onCompleted({ result: false, error: e, executionTime });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [test, assertion]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TestCard
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
status={getStatus(isRunning, isPassed)}
|
||||||
|
error={null}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Test;
|
||||||
62
sasjs-tests/src/components/TestCard.scss
Normal file
62
sasjs-tests/src/components/TestCard.scss
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
.test {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 8px;
|
||||||
|
margin: 8px;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
width: 20%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #eee;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description,
|
||||||
|
.execution-time {
|
||||||
|
color: #c6c0c0;
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
min-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.execution-time {
|
||||||
|
color: #f9e804;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
margin-right: 8px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
&.running {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.passed {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.failed {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 900px) {
|
||||||
|
.test {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 901px) and (max-width: 1280px) {
|
||||||
|
.test {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
sasjs-tests/src/components/TestCard.tsx
Normal file
43
sasjs-tests/src/components/TestCard.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React, { ReactElement } from "react";
|
||||||
|
import "./TestCard.scss";
|
||||||
|
|
||||||
|
interface TestCardProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
status: string;
|
||||||
|
error: Error | null;
|
||||||
|
executionTime?: number;
|
||||||
|
}
|
||||||
|
const TestCard = (props: TestCardProps): ReactElement<TestCardProps> => {
|
||||||
|
const { title, description, status, error, executionTime } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="test">
|
||||||
|
<code className="title">{title}</code>
|
||||||
|
<span className="description">{description}</span>
|
||||||
|
<span className="execution-time">
|
||||||
|
{executionTime ? executionTime.toFixed(2) + "s" : ""}
|
||||||
|
</span>
|
||||||
|
{status === "running" && ( // FIXME: use switch statement
|
||||||
|
<div>
|
||||||
|
<span className="icon running"></span>Running...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{status === "passed" && (
|
||||||
|
<div>
|
||||||
|
<span className="icon passed"></span>Passed
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{status === "failed" && (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<span className="icon failed"></span>Failed
|
||||||
|
</div>
|
||||||
|
{!!error && <code>{error.message}</code>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TestCard;
|
||||||
101
sasjs-tests/src/components/TestSuite.tsx
Normal file
101
sasjs-tests/src/components/TestSuite.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import React, { ReactElement, useState, useEffect } from "react";
|
||||||
|
import "./TestSuiteCard.scss";
|
||||||
|
import { Test } from "../types";
|
||||||
|
import TestComponent from "./Test";
|
||||||
|
import TestCard from "./TestCard";
|
||||||
|
|
||||||
|
interface TestSuiteProps {
|
||||||
|
name: string;
|
||||||
|
tests: Test[];
|
||||||
|
beforeAll?: (...args: any) => Promise<any>;
|
||||||
|
afterAll?: (...args: any) => Promise<any>;
|
||||||
|
onCompleted: (
|
||||||
|
name: string,
|
||||||
|
completedTests: {
|
||||||
|
test: Test;
|
||||||
|
result: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
executionTime: number;
|
||||||
|
}[]
|
||||||
|
) => void;
|
||||||
|
}
|
||||||
|
const TestSuite = (props: TestSuiteProps): ReactElement<TestSuiteProps> => {
|
||||||
|
const { name, tests, beforeAll, afterAll, onCompleted } = props;
|
||||||
|
const [context, setContext] = useState<any>(null);
|
||||||
|
const [completedTests, setCompletedTests] = useState< // TODO: create an interface
|
||||||
|
{
|
||||||
|
test: Test;
|
||||||
|
result: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
executionTime: number;
|
||||||
|
}[]
|
||||||
|
>([]);
|
||||||
|
const [currentTest, setCurrentTest] = useState<Test | null>(
|
||||||
|
(null as unknown) as Test // ?
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (beforeAll) beforeAll().then((data) => setContext({ data }))
|
||||||
|
}, [beforeAll]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tests.length) setCurrentTest(tests[0])
|
||||||
|
|
||||||
|
setCompletedTests([]);
|
||||||
|
setContext(null);
|
||||||
|
}, [tests]);
|
||||||
|
|
||||||
|
return (!!beforeAll && !!context) || !beforeAll ? ( // ?
|
||||||
|
<div className="test-suite">
|
||||||
|
<div className="test-suite-name running">{name}</div>
|
||||||
|
{currentTest && (
|
||||||
|
<TestComponent
|
||||||
|
{...currentTest}
|
||||||
|
context={context}
|
||||||
|
onCompleted={(completedTest) => {
|
||||||
|
const newCompleteTests = [
|
||||||
|
...completedTests,
|
||||||
|
{
|
||||||
|
test: currentTest,
|
||||||
|
result: completedTest.result,
|
||||||
|
error: completedTest.error,
|
||||||
|
executionTime: completedTest.executionTime,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
setCompletedTests(newCompleteTests);
|
||||||
|
|
||||||
|
const currentIndex = tests.indexOf(currentTest);
|
||||||
|
const nextIndex = currentIndex < tests.length - 1 ? currentIndex + 1 : -1;
|
||||||
|
|
||||||
|
if (nextIndex >= 0) setCurrentTest(tests[nextIndex]);
|
||||||
|
else setCurrentTest(null);
|
||||||
|
|
||||||
|
if (newCompleteTests.length === tests.length) {
|
||||||
|
if (afterAll) afterAll().then(() => onCompleted(name, newCompleteTests))
|
||||||
|
else onCompleted(name, newCompleteTests)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{completedTests.map((test, index) => {
|
||||||
|
const { test, result, error } = test;
|
||||||
|
const { title, description } = test;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TestCard
|
||||||
|
key={index}
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
status={result === true ? "passed" : "failed"}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></> // FIXME: use {null} instead
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TestSuite;
|
||||||
19
sasjs-tests/src/components/TestSuiteCard.scss
Normal file
19
sasjs-tests/src/components/TestSuiteCard.scss
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
.test-suite {
|
||||||
|
.test-suite-name {
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1f2027;
|
||||||
|
|
||||||
|
&.passed {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.failed {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.running {
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
sasjs-tests/src/components/TestSuiteCard.tsx
Normal file
43
sasjs-tests/src/components/TestSuiteCard.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React, { ReactElement } from "react";
|
||||||
|
import "./TestSuiteCard.scss";
|
||||||
|
import { Test } from "../types";
|
||||||
|
import TestCard from "./TestCard";
|
||||||
|
|
||||||
|
interface TestSuiteCardProps {
|
||||||
|
name: string;
|
||||||
|
tests: {
|
||||||
|
test: Test;
|
||||||
|
result: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
executionTime: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
const TestSuiteCard = (props: TestSuiteCardProps): ReactElement<TestSuiteCardProps> => {
|
||||||
|
const { name, tests } = props;
|
||||||
|
const overallStatus = tests.map((t) => t.result).reduce((x, y) => x && y); // TODO: refactor variable names
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="test-suite">
|
||||||
|
<div className={`test-suite-name ${overallStatus ? "passed" : "failed"}`}>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
{tests.map((test, index) => {
|
||||||
|
const { test, result, error, executionTime } = test;
|
||||||
|
const { title, description } = test;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TestCard
|
||||||
|
key={index}
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
status={result === true ? "passed" : "failed"}
|
||||||
|
error={error}
|
||||||
|
executionTime={executionTime}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TestSuiteCard;
|
||||||
52
sasjs-tests/src/context/AppContext.tsx
Normal file
52
sasjs-tests/src/context/AppContext.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import React, { createContext, useState, useEffect, ReactNode } from "react";
|
||||||
|
import SASjs from "sasjs";
|
||||||
|
|
||||||
|
export const AppContext = createContext<{ // TODO: create an interface
|
||||||
|
config: any; // TODO: be more specific on type declaration
|
||||||
|
sasJsConfig: any; // TODO: be more specific on type declaration
|
||||||
|
isLoggedIn: boolean;
|
||||||
|
setIsLoggedIn: (value: boolean) => void;
|
||||||
|
adapter: SASjs;
|
||||||
|
}>({
|
||||||
|
config: null,
|
||||||
|
sasJsConfig: null,
|
||||||
|
isLoggedIn: false,
|
||||||
|
setIsLoggedIn: (null as unknown) as (value: boolean) => void,
|
||||||
|
adapter: (null as unknown) as SASjs,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AppProvider = (props: { children: ReactNode }) => {
|
||||||
|
const [config, setConfig] = useState<{ sasJsConfig: any }>({sasJsConfig: null}); // TODO: be more specific on type declaration
|
||||||
|
const [adapter, setAdapter] = useState<SASjs>((null as unknown) as SASjs);
|
||||||
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch("config.json") // TODO: use axios instead of fetch
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((configJson: any) => { // TODO: be more specific on type declaration
|
||||||
|
setConfig(configJson);
|
||||||
|
|
||||||
|
const sasjs = new SASjs(configJson.sasJsConfig);
|
||||||
|
|
||||||
|
setAdapter(sasjs);
|
||||||
|
|
||||||
|
sasjs.checkSession().then((response) => {
|
||||||
|
setIsLoggedIn(response.isLoggedIn);
|
||||||
|
}); // FIXME: add catch block
|
||||||
|
});// FIXME: add catch block
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppContext.Provider
|
||||||
|
value={{
|
||||||
|
config,
|
||||||
|
sasJsConfig: config.sasJsConfig,
|
||||||
|
isLoggedIn,
|
||||||
|
setIsLoggedIn,
|
||||||
|
adapter,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</AppContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans",
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||||
"Droid Sans", "Helvetica Neue", sans-serif;
|
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||||
|
sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
background-color: #1f2027;
|
background-color: #1f2027;
|
||||||
@@ -9,7 +10,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
|
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||||
|
monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import ReactDOM from "react-dom";
|
|||||||
import { Route, HashRouter, Switch } from "react-router-dom";
|
import { Route, HashRouter, Switch } from "react-router-dom";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import * as serviceWorker from "./serviceWorker";
|
import * as serviceWorker from "./serviceWorker";
|
||||||
import { AppProvider } from "@sasjs/test-framework";
|
import { AppProvider } from "./context/AppContext";
|
||||||
import PrivateRoute from "./PrivateRoute";
|
import PrivateRoute from "./PrivateRoute";
|
||||||
import Login from "./Login";
|
import Login from "./Login";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
// opt-in, read https://bit.ly/CRA-PWA
|
// opt-in, read https://bit.ly/CRA-PWA
|
||||||
|
|
||||||
const isLocalhost = Boolean(
|
const isLocalhost = Boolean(
|
||||||
window.location.hostname === "localhost" ||
|
window.location.hostname === 'localhost' ||
|
||||||
// [::1] is the IPv6 localhost address.
|
// [::1] is the IPv6 localhost address.
|
||||||
window.location.hostname === "[::1]" ||
|
window.location.hostname === '[::1]' ||
|
||||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||||
window.location.hostname.match(
|
window.location.hostname.match(
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
@@ -21,7 +21,7 @@ const isLocalhost = Boolean(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export function register(config) {
|
export function register(config) {
|
||||||
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
// The URL constructor is available in all browsers that support SW.
|
// The URL constructor is available in all browsers that support SW.
|
||||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||||
if (publicUrl.origin !== window.location.origin) {
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
@@ -31,7 +31,7 @@ export function register(config) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener('load', () => {
|
||||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||||
|
|
||||||
if (isLocalhost) {
|
if (isLocalhost) {
|
||||||
@@ -42,8 +42,8 @@ export function register(config) {
|
|||||||
// service worker/PWA documentation.
|
// service worker/PWA documentation.
|
||||||
navigator.serviceWorker.ready.then(() => {
|
navigator.serviceWorker.ready.then(() => {
|
||||||
console.log(
|
console.log(
|
||||||
"This web app is being served cache-first by a service " +
|
'This web app is being served cache-first by a service ' +
|
||||||
"worker. To learn more, visit https://bit.ly/CRA-PWA"
|
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -57,21 +57,21 @@ export function register(config) {
|
|||||||
function registerValidSW(swUrl, config) {
|
function registerValidSW(swUrl, config) {
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
.register(swUrl)
|
.register(swUrl)
|
||||||
.then((registration) => {
|
.then(registration => {
|
||||||
registration.onupdatefound = () => {
|
registration.onupdatefound = () => {
|
||||||
const installingWorker = registration.installing;
|
const installingWorker = registration.installing;
|
||||||
if (installingWorker == null) {
|
if (installingWorker == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
installingWorker.onstatechange = () => {
|
installingWorker.onstatechange = () => {
|
||||||
if (installingWorker.state === "installed") {
|
if (installingWorker.state === 'installed') {
|
||||||
if (navigator.serviceWorker.controller) {
|
if (navigator.serviceWorker.controller) {
|
||||||
// At this point, the updated precached content has been fetched,
|
// At this point, the updated precached content has been fetched,
|
||||||
// but the previous service worker will still serve the older
|
// but the previous service worker will still serve the older
|
||||||
// content until all client tabs are closed.
|
// content until all client tabs are closed.
|
||||||
console.log(
|
console.log(
|
||||||
"New content is available and will be used when all " +
|
'New content is available and will be used when all ' +
|
||||||
"tabs for this page are closed. See https://bit.ly/CRA-PWA."
|
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
@@ -82,7 +82,7 @@ function registerValidSW(swUrl, config) {
|
|||||||
// At this point, everything has been precached.
|
// At this point, everything has been precached.
|
||||||
// It's the perfect time to display a
|
// It's the perfect time to display a
|
||||||
// "Content is cached for offline use." message.
|
// "Content is cached for offline use." message.
|
||||||
console.log("Content is cached for offline use.");
|
console.log('Content is cached for offline use.');
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
if (config && config.onSuccess) {
|
if (config && config.onSuccess) {
|
||||||
@@ -93,25 +93,25 @@ function registerValidSW(swUrl, config) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
console.error("Error during service worker registration:", error);
|
console.error('Error during service worker registration:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkValidServiceWorker(swUrl, config) {
|
function checkValidServiceWorker(swUrl, config) {
|
||||||
// Check if the service worker can be found. If it can't reload the page.
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
fetch(swUrl, {
|
fetch(swUrl, {
|
||||||
headers: { "Service-Worker": "script" }
|
headers: { 'Service-Worker': 'script' },
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then(response => {
|
||||||
// Ensure service worker exists, and that we really are getting a JS file.
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
const contentType = response.headers.get("content-type");
|
const contentType = response.headers.get('content-type');
|
||||||
if (
|
if (
|
||||||
response.status === 404 ||
|
response.status === 404 ||
|
||||||
(contentType != null && contentType.indexOf("javascript") === -1)
|
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||||
) {
|
) {
|
||||||
// No service worker found. Probably a different app. Reload the page.
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
navigator.serviceWorker.ready.then((registration) => {
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
registration.unregister().then(() => {
|
registration.unregister().then(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
@@ -123,18 +123,18 @@ function checkValidServiceWorker(swUrl, config) {
|
|||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log(
|
console.log(
|
||||||
"No internet connection found. App is running in offline mode."
|
'No internet connection found. App is running in offline mode.'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unregister() {
|
export function unregister() {
|
||||||
if ("serviceWorker" in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.ready
|
navigator.serviceWorker.ready
|
||||||
.then((registration) => {
|
.then(registration => {
|
||||||
registration.unregister();
|
registration.unregister();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(error => {
|
||||||
console.error(error.message);
|
console.error(error.message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
// allows you to do things like:
|
// allows you to do things like:
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
import "@testing-library/jest-dom/extend-expect";
|
import '@testing-library/jest-dom/extend-expect';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import SASjs, { ServerType, SASjsConfig } from "@sasjs/adapter";
|
import SASjs, { ServerType, SASjsConfig } from "sasjs";
|
||||||
import { TestSuite } from "@sasjs/test-framework";
|
import { TestSuite } from "../types";
|
||||||
|
|
||||||
const defaultConfig: SASjsConfig = {
|
const defaultConfig: SASjsConfig = {
|
||||||
serverUrl: window.location.origin,
|
serverUrl: window.location.origin,
|
||||||
@@ -9,7 +9,6 @@ const defaultConfig: SASjsConfig = {
|
|||||||
serverType: ServerType.SASViya,
|
serverType: ServerType.SASViya,
|
||||||
debug: true,
|
debug: true,
|
||||||
contextName: "SAS Job Execution compute context",
|
contextName: "SAS Job Execution compute context",
|
||||||
useComputeApi: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const customConfig = {
|
const customConfig = {
|
||||||
@@ -18,7 +17,7 @@ const customConfig = {
|
|||||||
pathSASViya: "viya",
|
pathSASViya: "viya",
|
||||||
appLoc: "/Public/seedapp",
|
appLoc: "/Public/seedapp",
|
||||||
serverType: ServerType.SAS9,
|
serverType: ServerType.SAS9,
|
||||||
debug: false
|
debug: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const basicTests = (
|
export const basicTests = (
|
||||||
@@ -34,18 +33,16 @@ export const basicTests = (
|
|||||||
test: async () => {
|
test: async () => {
|
||||||
return adapter.logIn(userName, password);
|
return adapter.logIn(userName, password);
|
||||||
},
|
},
|
||||||
assertion: (response: any) =>
|
assertion: (response: any) => // FIXME: be more specific on type declaration
|
||||||
response && response.isLoggedIn && response.userName === userName
|
response && response.isLoggedIn && response.userName === userName,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Default config",
|
title: "Default config",
|
||||||
description:
|
description: "Should instantiate with default config when none is provided",
|
||||||
"Should instantiate with default config when none is provided",
|
test: async () => Promise.resolve(new SASjs()),
|
||||||
test: async () => {
|
|
||||||
return Promise.resolve(new SASjs());
|
|
||||||
},
|
|
||||||
assertion: (sasjsInstance: SASjs) => {
|
assertion: (sasjsInstance: SASjs) => {
|
||||||
const sasjsConfig = sasjsInstance.getSasjsConfig();
|
const sasjsConfig = sasjsInstance.getSasjsConfig();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
sasjsConfig.serverUrl === defaultConfig.serverUrl &&
|
sasjsConfig.serverUrl === defaultConfig.serverUrl &&
|
||||||
sasjsConfig.pathSAS9 === defaultConfig.pathSAS9 &&
|
sasjsConfig.pathSAS9 === defaultConfig.pathSAS9 &&
|
||||||
@@ -54,7 +51,7 @@ export const basicTests = (
|
|||||||
sasjsConfig.serverType === defaultConfig.serverType &&
|
sasjsConfig.serverType === defaultConfig.serverType &&
|
||||||
sasjsConfig.debug === defaultConfig.debug
|
sasjsConfig.debug === defaultConfig.debug
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Custom config",
|
title: "Custom config",
|
||||||
@@ -72,7 +69,7 @@ export const basicTests = (
|
|||||||
sasjsConfig.serverType === customConfig.serverType &&
|
sasjsConfig.serverType === customConfig.serverType &&
|
||||||
sasjsConfig.debug === customConfig.debug
|
sasjsConfig.debug === customConfig.debug
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Config overrides",
|
title: "Config overrides",
|
||||||
@@ -92,7 +89,7 @@ export const basicTests = (
|
|||||||
sasjsConfig.serverType === defaultConfig.serverType &&
|
sasjsConfig.serverType === defaultConfig.serverType &&
|
||||||
sasjsConfig.debug === false
|
sasjsConfig.debug === false
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,45 +1,44 @@
|
|||||||
import SASjs from "@sasjs/adapter";
|
import SASjs from "sasjs";
|
||||||
import { TestSuite } from "@sasjs/test-framework";
|
import { TestSuite } from "../types";
|
||||||
|
|
||||||
const stringData: any = { table1: [{ col1: "first col value" }] };
|
const stringData: any = { table1: [{ col1: "first col value" }] }; // TODO: be more specific on type declaration
|
||||||
const numericData: any = { table1: [{ col1: 3.14159265 }] };
|
const numericData: any = { table1: [{ col1: 3.14159265 }] }; // TODO: be more specific on type declaration
|
||||||
const multiColumnData: any = {
|
const multiColumnData: any = { // TODO: be more specific on type declaration
|
||||||
table1: [{ col1: 42, col2: 1.618, col3: "x", col4: "x" }]
|
table1: [{ col1: 42, col2: 1.618, col3: "x", col4: "x" }],
|
||||||
};
|
};
|
||||||
const multipleRowsWithNulls: any = {
|
const multipleRowsWithNulls: any = { // TODO: be more specific on type declaration
|
||||||
table1: [
|
table1: [
|
||||||
{ col1: 42, col2: null, col3: "x", col4: "" },
|
{ col1: 42, col2: null, col3: "x", col4: "" },
|
||||||
{ col1: 42, col2: null, col3: "x", col4: "" },
|
{ col1: 42, col2: null, col3: "x", col4: "" },
|
||||||
{ col1: 42, col2: null, col3: "x", col4: "" },
|
{ col1: 42, col2: null, col3: "x", col4: "" },
|
||||||
{ col1: 42, col2: 1.62, col3: "x", col4: "x" },
|
{ col1: 42, col2: 1.62, col3: "x", col4: "x" },
|
||||||
{ col1: 42, col2: 1.62, col3: "x", col4: "x" }
|
{ col1: 42, col2: 1.62, col3: "x", col4: "x" },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
const multipleColumnsWithNulls: any = {
|
const multipleColumnsWithNulls: any = { // TODO: be more specific on type declaration
|
||||||
table1: [
|
table1: [
|
||||||
{ col1: 42, col2: null, col3: "x", col4: null },
|
{ col1: 42, col2: null, col3: "x", col4: null },
|
||||||
{ col1: 42, col2: null, col3: "x", col4: null },
|
{ col1: 42, col2: null, col3: "x", col4: null },
|
||||||
{ col1: 42, col2: null, col3: "x", col4: null },
|
{ col1: 42, col2: null, col3: "x", col4: null },
|
||||||
{ col1: 42, col2: null, col3: "x", col4: "" },
|
{ col1: 42, col2: null, col3: "x", col4: "" },
|
||||||
{ col1: 42, col2: null, col3: "x", col4: "" }
|
{ col1: 42, col2: null, col3: "x", col4: "" },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLongStringData = (length = 32764) => {
|
const getLongStringData = (length = 32764) => { // FIXME: add type declaration
|
||||||
let x = "X";
|
let x = "X";
|
||||||
for (let i = 1; i <= length; i++) {
|
|
||||||
x = x + "X";
|
for (let i = 1; i <= length; i++) x += 'X'
|
||||||
}
|
|
||||||
const data: any = { table1: [{ col1: x }] };
|
const data: any = { table1: [{ col1: x }] }; // TODO: be more specific on type declaration
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLargeObjectData = () => {
|
const getLargeObjectData = () => {
|
||||||
const data = { table1: [{ big: "data" }] };
|
const data = { table1: [{ big: "data" }] };
|
||||||
|
|
||||||
for (let i = 1; i < 10000; i++) {
|
for (let i = 1; i < 10000; i++) data.table1.push(data.table1[0])
|
||||||
data.table1.push(data.table1[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
@@ -50,12 +49,8 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
|||||||
{
|
{
|
||||||
title: "Single string value",
|
title: "Single string value",
|
||||||
description: "Should send an array with a single string value",
|
description: "Should send an array with a single string value",
|
||||||
test: () => {
|
test: () => adapter.request("common/sendArr", stringData),
|
||||||
return adapter.request("common/sendArr", stringData);
|
assertion: (res: any) => res.table1[0][0] === stringData.table1[0].col1 // TODO: be more specific on type declaration
|
||||||
},
|
|
||||||
assertion: (res: any) => {
|
|
||||||
return res.table1[0][0] === stringData.table1[0].col1;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Long string value",
|
title: "Long string value",
|
||||||
@@ -64,22 +59,18 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
|||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", getLongStringData());
|
return adapter.request("common/sendArr", getLongStringData());
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => res.table1[0][0] === getLongStringData().table1[0].col1 // TODO: be more specific on type declaration
|
||||||
const longStringData = getLongStringData();
|
|
||||||
return res.table1[0][0] === longStringData.table1[0].col1;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Overly long string value",
|
title: "Overly long string value",
|
||||||
description:
|
description:
|
||||||
"Should error out with long string values over 32765 characters",
|
"Should error out with long string values over 32765 characters",
|
||||||
test: () => {
|
test: () => adapter
|
||||||
const data = getLongStringData(32767);
|
.request("common/sendArr", getLongStringData(32767))
|
||||||
return adapter.request("common/sendArr", data).catch((e) => e);
|
.catch((e) => e), // TODO: rename
|
||||||
|
assertion: (error: any) => { // TODO: be more specific on type declaration
|
||||||
|
return !!error && !!error.MESSAGE; // FIXME: refactor
|
||||||
},
|
},
|
||||||
assertion: (error: any) => {
|
|
||||||
return !!error && !!error.MESSAGE;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Single numeric value",
|
title: "Single numeric value",
|
||||||
@@ -87,9 +78,9 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
|||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", numericData);
|
return adapter.request("common/sendArr", numericData);
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||||
return res.table1[0][0] === numericData.table1[0].col1;
|
return res.table1[0][0] === numericData.table1[0].col1;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multiple columns",
|
title: "Multiple columns",
|
||||||
@@ -97,14 +88,14 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
|||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", multiColumnData);
|
return adapter.request("common/sendArr", multiColumnData);
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||||
return (
|
return (
|
||||||
res.table1[0][0] === multiColumnData.table1[0].col1 &&
|
res.table1[0][0] === multiColumnData.table1[0].col1 &&
|
||||||
res.table1[0][1] === multiColumnData.table1[0].col2 &&
|
res.table1[0][1] === multiColumnData.table1[0].col2 &&
|
||||||
res.table1[0][2] === multiColumnData.table1[0].col3 &&
|
res.table1[0][2] === multiColumnData.table1[0].col3 &&
|
||||||
res.table1[0][3] === multiColumnData.table1[0].col4
|
res.table1[0][3] === multiColumnData.table1[0].col4
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multiple rows with nulls",
|
title: "Multiple rows with nulls",
|
||||||
@@ -112,9 +103,10 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
|||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", multipleRowsWithNulls);
|
return adapter.request("common/sendArr", multipleRowsWithNulls);
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||||
let result = true;
|
let result = true;
|
||||||
multipleRowsWithNulls.table1.forEach((_: any, index: number) => {
|
multipleRowsWithNulls.table1.forEach((_: any, index: number) => { // TODO: be more specific on type declaration
|
||||||
|
// FIXME: use loop
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index][0] === multipleRowsWithNulls.table1[index].col1;
|
res.table1[index][0] === multipleRowsWithNulls.table1[index].col1;
|
||||||
@@ -128,8 +120,9 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
|||||||
result &&
|
result &&
|
||||||
res.table1[index][3] === multipleRowsWithNulls.table1[index].col4;
|
res.table1[index][3] === multipleRowsWithNulls.table1[index].col4;
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multiple columns with nulls",
|
title: "Multiple columns with nulls",
|
||||||
@@ -137,9 +130,9 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
|||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", multipleColumnsWithNulls);
|
return adapter.request("common/sendArr", multipleColumnsWithNulls);
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||||
let result = true;
|
let result = true;
|
||||||
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => {
|
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => { // TODO: be more specific on type declaration
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index][0] ===
|
res.table1[index][0] ===
|
||||||
@@ -158,9 +151,9 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
|||||||
(multipleColumnsWithNulls.table1[index].col4 || "");
|
(multipleColumnsWithNulls.table1[index].col4 || "");
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||||
@@ -170,12 +163,12 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
title: "Invalid column name",
|
title: "Invalid column name",
|
||||||
description: "Should throw an error",
|
description: "Should throw an error",
|
||||||
test: async () => {
|
test: async () => {
|
||||||
const invalidData: any = {
|
const invalidData: any = { // TODO: be more specific on type declaration
|
||||||
"1 invalid table": [{ col1: 42 }]
|
"1 invalid table": [{ col1: 42 }],
|
||||||
};
|
};
|
||||||
return adapter.request("common/sendObj", invalidData).catch((e) => e);
|
return adapter.request("common/sendObj", invalidData).catch((e) => e);
|
||||||
},
|
},
|
||||||
assertion: (error: any) => !!error && !!error.MESSAGE
|
assertion: (error: any) => !!error && !!error.MESSAGE, // TODO: be more specific on type declaration
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Single string value",
|
title: "Single string value",
|
||||||
@@ -183,9 +176,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", stringData);
|
return adapter.request("common/sendObj", stringData);
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||||
return res.table1[0].COL1 === stringData.table1[0].col1;
|
return res.table1[0].COL1 === stringData.table1[0].col1;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Long string value",
|
title: "Long string value",
|
||||||
@@ -194,10 +187,10 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", getLongStringData());
|
return adapter.request("common/sendObj", getLongStringData());
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||||
const longStringData = getLongStringData();
|
const longStringData = getLongStringData();
|
||||||
return res.table1[0].COL1 === longStringData.table1[0].col1;
|
return res.table1[0].COL1 === longStringData.table1[0].col1;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Overly long string value",
|
title: "Overly long string value",
|
||||||
@@ -208,9 +201,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
.request("common/sendObj", getLongStringData(32767))
|
.request("common/sendObj", getLongStringData(32767))
|
||||||
.catch((e) => e);
|
.catch((e) => e);
|
||||||
},
|
},
|
||||||
assertion: (error: any) => {
|
assertion: (error: any) => { // TODO: be more specific on type declaration
|
||||||
return !!error && !!error.MESSAGE;
|
return !!error && !!error.MESSAGE;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Single numeric value",
|
title: "Single numeric value",
|
||||||
@@ -218,9 +211,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", numericData);
|
return adapter.request("common/sendObj", numericData);
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||||
return res.table1[0].COL1 === numericData.table1[0].col1;
|
return res.table1[0].COL1 === numericData.table1[0].col1;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -229,10 +222,10 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", getLargeObjectData());
|
return adapter.request("common/sendObj", getLargeObjectData());
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||||
const data = getLargeObjectData();
|
const data = getLargeObjectData();
|
||||||
return res.table1[9000].BIG === data.table1[9000].big;
|
return res.table1[9000].BIG === data.table1[9000].big;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multiple columns",
|
title: "Multiple columns",
|
||||||
@@ -240,14 +233,14 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", multiColumnData);
|
return adapter.request("common/sendObj", multiColumnData);
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||||
return (
|
return (
|
||||||
res.table1[0].COL1 === multiColumnData.table1[0].col1 &&
|
res.table1[0].COL1 === multiColumnData.table1[0].col1 &&
|
||||||
res.table1[0].COL2 === multiColumnData.table1[0].col2 &&
|
res.table1[0].COL2 === multiColumnData.table1[0].col2 &&
|
||||||
res.table1[0].COL3 === multiColumnData.table1[0].col3 &&
|
res.table1[0].COL3 === multiColumnData.table1[0].col3 &&
|
||||||
res.table1[0].COL4 === multiColumnData.table1[0].col4
|
res.table1[0].COL4 === multiColumnData.table1[0].col4
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multiple rows with nulls",
|
title: "Multiple rows with nulls",
|
||||||
@@ -255,9 +248,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", multipleRowsWithNulls);
|
return adapter.request("common/sendObj", multipleRowsWithNulls);
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||||
let result = true;
|
let result = true;
|
||||||
multipleRowsWithNulls.table1.forEach((_: any, index: number) => {
|
multipleRowsWithNulls.table1.forEach((_: any, index: number) => { // TODO: be more specific on type declaration
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index].COL1 === multipleRowsWithNulls.table1[index].col1;
|
res.table1[index].COL1 === multipleRowsWithNulls.table1[index].col1;
|
||||||
@@ -272,7 +265,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
res.table1[index].COL4 === multipleRowsWithNulls.table1[index].col4;
|
res.table1[index].COL4 === multipleRowsWithNulls.table1[index].col4;
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multiple columns with nulls",
|
title: "Multiple columns with nulls",
|
||||||
@@ -280,9 +273,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", multipleColumnsWithNulls);
|
return adapter.request("common/sendObj", multipleColumnsWithNulls);
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => { // TODO: be more specific on type declaration
|
||||||
let result = true;
|
let result = true;
|
||||||
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => {
|
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => { // TODO: be more specific on type declaration
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index].COL1 ===
|
res.table1[index].COL1 ===
|
||||||
@@ -301,7 +294,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
(multipleColumnsWithNulls.table1[index].col4 || "");
|
(multipleColumnsWithNulls.table1[index].col4 || "");
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import SASjs from "@sasjs/adapter";
|
import SASjs from "sasjs";
|
||||||
import { TestSuite } from "@sasjs/test-framework";
|
import { TestSuite } from "../types";
|
||||||
|
|
||||||
const data: any = { table1: [{ col1: "first col value" }] };
|
const data: any = { table1: [{ col1: "first col value" }] }; // TODO: be more specific on type declaration
|
||||||
|
|
||||||
export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
|
export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
|
||||||
name: "SASjs Requests",
|
name: "SASjs Requests",
|
||||||
@@ -9,42 +9,12 @@ export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
|
|||||||
{
|
{
|
||||||
title: "WORK tables",
|
title: "WORK tables",
|
||||||
description: "Should get WORK tables after request",
|
description: "Should get WORK tables after request",
|
||||||
test: async () => {
|
test: async () => adapter.request("common/sendArr", data),
|
||||||
return adapter.request("common/sendArr", data);
|
assertion: (res: any) => {
|
||||||
},
|
|
||||||
assertion: () => {
|
|
||||||
const requests = adapter.getSasRequests();
|
const requests = adapter.getSasRequests();
|
||||||
if (adapter.getSasjsConfig().debug) {
|
|
||||||
return requests[0].SASWORK !== null;
|
|
||||||
} else {
|
|
||||||
return requests[0].SASWORK === null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Make error and capture log",
|
|
||||||
description: "Should make an error and capture log",
|
|
||||||
test: async () => {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
adapter
|
|
||||||
.request("common/makeErr", data)
|
|
||||||
.then((res) => {
|
|
||||||
//no action here, this request must throw error
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
let sasRequests = adapter.getSasRequests();
|
|
||||||
let makeErrRequest =
|
|
||||||
sasRequests.find((req) =>
|
|
||||||
req.serviceLink.includes("makeErr")
|
|
||||||
) || null;
|
|
||||||
|
|
||||||
resolve(!!makeErrRequest);
|
return adapter.getSasjsConfig().debug ? requests[0].SASWORK !== null : requests[0].SASWORK === null
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
assertion: (response) => {
|
},
|
||||||
return response;
|
],
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import SASjs from "@sasjs/adapter";
|
import SASjs from "sasjs";
|
||||||
import { TestSuite } from "@sasjs/test-framework";
|
import { TestSuite } from "../types";
|
||||||
|
|
||||||
const specialCharData: any = {
|
const specialCharData: any = { // TODO: be more specific on type definition
|
||||||
table1: [
|
table1: [
|
||||||
{
|
{
|
||||||
tab: "\t",
|
tab: "\t",
|
||||||
@@ -9,16 +9,16 @@ const specialCharData: any = {
|
|||||||
cr: "\r",
|
cr: "\r",
|
||||||
semicolon: ";semi",
|
semicolon: ";semi",
|
||||||
percent: "%",
|
percent: "%",
|
||||||
singleQuote: "'",
|
singleQuote: "'", // TODO: use ``
|
||||||
doubleQuote: '"',
|
doubleQuote: '"', // TODO: use ``
|
||||||
crlf: "\r\n",
|
crlf: "\r\n",
|
||||||
euro: "€euro",
|
euro: "€euro",
|
||||||
banghash: "!#banghash"
|
banghash: "!#banghash",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const moreSpecialCharData: any = {
|
const moreSpecialCharData: any = { // TODO: be more specific on type definition
|
||||||
table1: [
|
table1: [
|
||||||
{
|
{
|
||||||
speech0: '"speech',
|
speech0: '"speech',
|
||||||
@@ -31,51 +31,53 @@ const moreSpecialCharData: any = {
|
|||||||
sigma: "Σsigma",
|
sigma: "Σsigma",
|
||||||
at: "@at",
|
at: "@at",
|
||||||
serbian: "Српски",
|
serbian: "Српски",
|
||||||
dollar: "$"
|
dollar: "$",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const getWideData = () => {
|
const getWideData = () => { // FIXME: declared but never used
|
||||||
const cols: any = {};
|
const cols: any = {}; // TODO: be more specific on type definition
|
||||||
for (let i = 1; i <= 10000; i++) {
|
for (let i = 1; i <= 10000; i++) { // Why 10000?
|
||||||
cols["col" + i] = "test" + i;
|
cols["col" + i] = "test" + i;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: any = {
|
const data: any = { // TODO: be more specific on type definition
|
||||||
table1: [cols]
|
table1: [cols],
|
||||||
};
|
};
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTables = () => {
|
const getTables = () => { // FIXME: declared but never used
|
||||||
const tables: any = {};
|
const tables: any = {}; // TODO: be more specific on type definition
|
||||||
|
|
||||||
for (let i = 1; i <= 100; i++) {
|
for (let i = 1; i <= 100; i++) { // why 100
|
||||||
tables["table" + i] = [{ col1: "x", col2: "x", col3: "x", col4: "x" }];
|
tables["table" + i] = [{ col1: "x", col2: "x", col3: "x", col4: "x" }];
|
||||||
}
|
}
|
||||||
|
|
||||||
return tables;
|
return tables;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLargeDataset = () => {
|
const getLargeDataset = () => {
|
||||||
const rows: any = [];
|
const rows: any = []; // TODO: be more specific on type definition
|
||||||
const colData: string =
|
const colData: string = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // FIXME: no need to explicitly mention data type
|
||||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
|
||||||
for (let i = 1; i <= 10000; i++) {
|
for (let i = 1; i <= 10000; i++) {
|
||||||
rows.push({ col1: colData, col2: colData, col3: colData, col4: colData });
|
rows.push({ col1: colData, col2: colData, col3: colData, col4: colData });
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: any = {
|
const data: any = { // TODO: be more specific on type definition
|
||||||
table1: rows
|
table1: rows,
|
||||||
};
|
};
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const errorAndCsrfData: any = {
|
// FIXME: declared but never used
|
||||||
|
const errorAndCsrfData: any = { // TODO: be more specific on type definition
|
||||||
error: [{ col1: "q", col2: "w", col3: "e", col4: "r" }],
|
error: [{ col1: "q", col2: "w", col3: "e", col4: "r" }],
|
||||||
_csrf: [{ col1: "q", col2: "w", col3: "e", col4: "r" }]
|
_csrf: [{ col1: "q", col2: "w", col3: "e", col4: "r" }],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
||||||
@@ -84,10 +86,8 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
|||||||
{
|
{
|
||||||
title: "Common special characters",
|
title: "Common special characters",
|
||||||
description: "Should handle common special characters",
|
description: "Should handle common special characters",
|
||||||
test: () => {
|
test: () => adapter.request("common/sendArr", specialCharData),
|
||||||
return adapter.request("common/sendArr", specialCharData);
|
assertion: (res: any) => { // TODO: be more specific on type definition
|
||||||
},
|
|
||||||
assertion: (res: any) => {
|
|
||||||
return (
|
return (
|
||||||
res.table1[0][0] === specialCharData.table1[0].tab &&
|
res.table1[0][0] === specialCharData.table1[0].tab &&
|
||||||
res.table1[0][1] === specialCharData.table1[0].lf &&
|
res.table1[0][1] === specialCharData.table1[0].lf &&
|
||||||
@@ -100,151 +100,138 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
|||||||
res.table1[0][8] === specialCharData.table1[0].euro &&
|
res.table1[0][8] === specialCharData.table1[0].euro &&
|
||||||
res.table1[0][9] === specialCharData.table1[0].banghash
|
res.table1[0][9] === specialCharData.table1[0].banghash
|
||||||
);
|
);
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Other special characters",
|
|
||||||
description: "Should handle other special characters",
|
|
||||||
test: () => {
|
|
||||||
return adapter.request("common/sendArr", moreSpecialCharData);
|
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
|
||||||
return (
|
|
||||||
res.table1[0][0] === moreSpecialCharData.table1[0].speech0 &&
|
|
||||||
res.table1[0][1] === moreSpecialCharData.table1[0].pct &&
|
|
||||||
res.table1[0][2] === moreSpecialCharData.table1[0].speech &&
|
|
||||||
res.table1[0][3] === moreSpecialCharData.table1[0].slash &&
|
|
||||||
res.table1[0][4] === moreSpecialCharData.table1[0].slashWithSpecial &&
|
|
||||||
res.table1[0][5] === moreSpecialCharData.table1[0].macvar &&
|
|
||||||
res.table1[0][6] === moreSpecialCharData.table1[0].chinese &&
|
|
||||||
res.table1[0][7] === moreSpecialCharData.table1[0].sigma &&
|
|
||||||
res.table1[0][8] === moreSpecialCharData.table1[0].at &&
|
|
||||||
res.table1[0][9] === moreSpecialCharData.table1[0].serbian &&
|
|
||||||
res.table1[0][10] === moreSpecialCharData.table1[0].dollar
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
// TODO: delete commented out code
|
||||||
|
|
||||||
|
// {
|
||||||
|
// title: "Other special characters",
|
||||||
|
// description: "Should handle other special characters",
|
||||||
|
// test: () => {
|
||||||
|
// return adapter.request("common/sendArr", moreSpecialCharData);
|
||||||
|
// },
|
||||||
|
// assertion: (res: any) => {
|
||||||
|
// return (
|
||||||
|
// res.table1[0][0] === moreSpecialCharData.table1[0].speech0 &&
|
||||||
|
// res.table1[0][1] === moreSpecialCharData.table1[0].pct &&
|
||||||
|
// res.table1[0][2] === moreSpecialCharData.table1[0].speech &&
|
||||||
|
// res.table1[0][3] === moreSpecialCharData.table1[0].slash &&
|
||||||
|
// res.table1[0][4] === moreSpecialCharData.table1[0].slashWithSpecial &&
|
||||||
|
// res.table1[0][5] === moreSpecialCharData.table1[0].macvar &&
|
||||||
|
// res.table1[0][6] === moreSpecialCharData.table1[0].chinese &&
|
||||||
|
// res.table1[0][7] === moreSpecialCharData.table1[0].sigma &&
|
||||||
|
// res.table1[0][8] === moreSpecialCharData.table1[0].at &&
|
||||||
|
// res.table1[0][9] === moreSpecialCharData.table1[0].serbian &&
|
||||||
|
// res.table1[0][10] === moreSpecialCharData.table1[0].dollar
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Wide table with sendArr",
|
||||||
|
// description: "Should handle data with 10000 columns",
|
||||||
|
// test: () => {
|
||||||
|
// return adapter.request("common/sendArr", getWideData());
|
||||||
|
// },
|
||||||
|
// assertion: (res: any) => {
|
||||||
|
// const data = getWideData();
|
||||||
|
// let result = true;
|
||||||
|
// for (let i = 0; i <= 10; i++) {
|
||||||
|
// result =
|
||||||
|
// result && res.table1[0][i] === data.table1[0]["col" + (i + 1)];
|
||||||
|
// }
|
||||||
|
// return result;
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Wide table with sendObj",
|
||||||
|
// description: "Should handle data with 10000 columns",
|
||||||
|
// test: () => {
|
||||||
|
// return adapter.request("common/sendObj", getWideData());
|
||||||
|
// },
|
||||||
|
// assertion: (res: any) => {
|
||||||
|
// const data = getWideData();
|
||||||
|
// let result = true;
|
||||||
|
// for (let i = 0; i <= 10; i++) {
|
||||||
|
// result =
|
||||||
|
// result &&
|
||||||
|
// res.table1[0]["COL" + (i + 1)] === data.table1[0]["col" + (i + 1)];
|
||||||
|
// }
|
||||||
|
// return result;
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Multiple tables",
|
||||||
|
// description: "Should handle data with 100 tables",
|
||||||
|
// test: () => {
|
||||||
|
// return adapter.request("common/sendArr", getTables());
|
||||||
|
// },
|
||||||
|
// assertion: (res: any) => {
|
||||||
|
// const data = getTables();
|
||||||
|
// return (
|
||||||
|
// res.table1[0][0] === data.table1[0].col1 &&
|
||||||
|
// res.table1[0][1] === data.table1[0].col2 &&
|
||||||
|
// res.table1[0][2] === data.table1[0].col3 &&
|
||||||
|
// res.table1[0][3] === data.table1[0].col4 &&
|
||||||
|
// res.table50[0][0] === data.table50[0].col1 &&
|
||||||
|
// res.table50[0][1] === data.table50[0].col2 &&
|
||||||
|
// res.table50[0][2] === data.table50[0].col3 &&
|
||||||
|
// res.table50[0][3] === data.table50[0].col4
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
title: "Wide table with sendArr",
|
title: "Large dataset",
|
||||||
description: "Should handle data with 10000 columns",
|
|
||||||
test: () => {
|
|
||||||
return adapter.request("common/sendArr", getWideData());
|
|
||||||
},
|
|
||||||
assertion: (res: any) => {
|
|
||||||
const data = getWideData();
|
|
||||||
let result = true;
|
|
||||||
for (let i = 0; i <= 10; i++) {
|
|
||||||
result =
|
|
||||||
result && res.table1[0][i] === data.table1[0]["col" + (i + 1)];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Wide table with sendObj",
|
|
||||||
description: "Should handle data with 10000 columns",
|
|
||||||
test: () => {
|
|
||||||
return adapter.request("common/sendObj", getWideData());
|
|
||||||
},
|
|
||||||
assertion: (res: any) => {
|
|
||||||
const data = getWideData();
|
|
||||||
let result = true;
|
|
||||||
for (let i = 0; i <= 10; i++) {
|
|
||||||
result =
|
|
||||||
result &&
|
|
||||||
res.table1[0]["COL" + (i + 1)] === data.table1[0]["col" + (i + 1)];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Multiple tables",
|
|
||||||
description: "Should handle data with 100 tables",
|
|
||||||
test: () => {
|
|
||||||
return adapter.request("common/sendArr", getTables());
|
|
||||||
},
|
|
||||||
assertion: (res: any) => {
|
|
||||||
const data = getTables();
|
|
||||||
return (
|
|
||||||
res.table1[0][0] === data.table1[0].col1 &&
|
|
||||||
res.table1[0][1] === data.table1[0].col2 &&
|
|
||||||
res.table1[0][2] === data.table1[0].col3 &&
|
|
||||||
res.table1[0][3] === data.table1[0].col4 &&
|
|
||||||
res.table50[0][0] === data.table50[0].col1 &&
|
|
||||||
res.table50[0][1] === data.table50[0].col2 &&
|
|
||||||
res.table50[0][2] === data.table50[0].col3 &&
|
|
||||||
res.table50[0][3] === data.table50[0].col4
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Large dataset with sendObj",
|
|
||||||
description: "Should handle 5mb of data",
|
description: "Should handle 5mb of data",
|
||||||
test: () => {
|
test: () => adapter.request("common/sendArr", getLargeDataset()),
|
||||||
return adapter.request("common/sendObj", getLargeDataset());
|
assertion: (res: any) => { // TODO: be more specific on type definition
|
||||||
},
|
|
||||||
assertion: (res: any) => {
|
|
||||||
const data = getLargeDataset();
|
const data = getLargeDataset();
|
||||||
let result = true;
|
let result = true; // TODO: rename
|
||||||
|
|
||||||
for (let i = 0; i <= 10; i++) {
|
for (let i = 0; i <= 10; i++) {
|
||||||
result = result && res.table1[i][0] === data.table1[i][0];
|
result = result && res.table1[i][0] === data.table1[i][0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Large dataset with sendArr",
|
|
||||||
description: "Should handle 5mb of data",
|
|
||||||
test: () => {
|
|
||||||
return adapter.request("common/sendArr", getLargeDataset());
|
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
|
||||||
const data = getLargeDataset();
|
|
||||||
let result = true;
|
|
||||||
for (let i = 0; i <= 10; i++) {
|
|
||||||
result =
|
|
||||||
result && res.table1[i][0] === Object.values(data.table1[i])[0];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Error and _csrf tables with sendArr",
|
// {
|
||||||
description: "Should handle error and _csrf tables",
|
// title: "Error and _csrf tables with sendArr",
|
||||||
test: () => {
|
// description: "Should handle error and _csrf tables",
|
||||||
return adapter.request("common/sendArr", errorAndCsrfData);
|
// test: () => {
|
||||||
},
|
// return adapter.request("common/sendArr", errorAndCsrfData);
|
||||||
assertion: (res: any) => {
|
// },
|
||||||
return (
|
// assertion: (res: any) => {
|
||||||
res.error[0][0] === errorAndCsrfData.error[0].col1 &&
|
// return (
|
||||||
res.error[0][1] === errorAndCsrfData.error[0].col2 &&
|
// res.error[0][0] === errorAndCsrfData.error[0].col1 &&
|
||||||
res.error[0][2] === errorAndCsrfData.error[0].col3 &&
|
// res.error[0][1] === errorAndCsrfData.error[0].col2 &&
|
||||||
res.error[0][3] === errorAndCsrfData.error[0].col4 &&
|
// res.error[0][2] === errorAndCsrfData.error[0].col3 &&
|
||||||
res._csrf[0][0] === errorAndCsrfData._csrf[0].col1 &&
|
// res.error[0][3] === errorAndCsrfData.error[0].col4 &&
|
||||||
res._csrf[0][1] === errorAndCsrfData._csrf[0].col2 &&
|
// res._csrf[0][0] === errorAndCsrfData._csrf[0].col1 &&
|
||||||
res._csrf[0][2] === errorAndCsrfData._csrf[0].col3 &&
|
// res._csrf[0][1] === errorAndCsrfData._csrf[0].col2 &&
|
||||||
res._csrf[0][3] === errorAndCsrfData._csrf[0].col4
|
// res._csrf[0][2] === errorAndCsrfData._csrf[0].col3 &&
|
||||||
);
|
// res._csrf[0][3] === errorAndCsrfData._csrf[0].col4
|
||||||
}
|
// );
|
||||||
},
|
// },
|
||||||
{
|
// },
|
||||||
title: "Error and _csrf tables with sendObj",
|
// {
|
||||||
description: "Should handle error and _csrf tables",
|
// title: "Error and _csrf tables with sendObj",
|
||||||
test: () => {
|
// description: "Should handle error and _csrf tables",
|
||||||
return adapter.request("common/sendObj", errorAndCsrfData);
|
// test: () => {
|
||||||
},
|
// return adapter.request("common/sendObj", errorAndCsrfData);
|
||||||
assertion: (res: any) => {
|
// },
|
||||||
return (
|
// assertion: (res: any) => {
|
||||||
res.error[0].COL1 === errorAndCsrfData.error[0].col1 &&
|
// return (
|
||||||
res.error[0].COL2 === errorAndCsrfData.error[0].col2 &&
|
// res.error[0].COL1 === errorAndCsrfData.error[0].col1 &&
|
||||||
res.error[0].COL3 === errorAndCsrfData.error[0].col3 &&
|
// res.error[0].COL2 === errorAndCsrfData.error[0].col2 &&
|
||||||
res.error[0].COL4 === errorAndCsrfData.error[0].col4 &&
|
// res.error[0].COL3 === errorAndCsrfData.error[0].col3 &&
|
||||||
res._csrf[0].COL1 === errorAndCsrfData._csrf[0].col1 &&
|
// res.error[0].COL4 === errorAndCsrfData.error[0].col4 &&
|
||||||
res._csrf[0].COL2 === errorAndCsrfData._csrf[0].col2 &&
|
// res._csrf[0].COL1 === errorAndCsrfData._csrf[0].col1 &&
|
||||||
res._csrf[0].COL3 === errorAndCsrfData._csrf[0].col3 &&
|
// res._csrf[0].COL2 === errorAndCsrfData._csrf[0].col2 &&
|
||||||
res._csrf[0].COL4 === errorAndCsrfData._csrf[0].col4
|
// res._csrf[0].COL3 === errorAndCsrfData._csrf[0].col3 &&
|
||||||
);
|
// res._csrf[0].COL4 === errorAndCsrfData._csrf[0].col4
|
||||||
}
|
// );
|
||||||
}
|
// },
|
||||||
]
|
// },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
15
sasjs-tests/src/types/index.ts
Normal file
15
sasjs-tests/src/types/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export interface Test {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
beforeTest?: (...args: any) => Promise<any>;
|
||||||
|
afterTest?: (...args: any) => Promise<any>;
|
||||||
|
test: (context?: any) => Promise<any>;
|
||||||
|
assertion: (...args: any) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TestSuite {
|
||||||
|
name: string;
|
||||||
|
tests: Test[];
|
||||||
|
beforeAll?: (...args: any) => Promise<any>;
|
||||||
|
afterAll?: (...args: any) => Promise<any>;
|
||||||
|
}
|
||||||
@@ -3,20 +3,21 @@ export const assert = (
|
|||||||
message = "Assertion failed"
|
message = "Assertion failed"
|
||||||
) => {
|
) => {
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof expression === "boolean") {
|
if (typeof expression === "boolean") result = expression;
|
||||||
result = expression;
|
else result = expression();
|
||||||
} else {
|
|
||||||
result = expression();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(message);
|
console.error(message);
|
||||||
|
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!!result) {
|
if (!!result) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
console.error(message);
|
console.error(message);
|
||||||
|
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
27
sasjs-tests/src/utils/UploadFile.ts
Normal file
27
sasjs-tests/src/utils/UploadFile.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
export const uploadFile = (file: File, fileName: string, url: string) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const data = new FormData();
|
||||||
|
data.append("file", file);
|
||||||
|
data.append("filename", fileName);
|
||||||
|
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.withCredentials = true;
|
||||||
|
xhr.addEventListener("readystatechange", function () { // TODO: use ES6
|
||||||
|
if (this.readyState === 4) {
|
||||||
|
let response: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = JSON.parse(this.responseText);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.open("POST", url);
|
||||||
|
xhr.setRequestHeader("cache-control", "no-cache");
|
||||||
|
xhr.send(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
import { isLogInRequired, needsRetry, isUrl } from './utils'
|
|
||||||
import { CsrfToken } from './types/CsrfToken'
|
|
||||||
import { UploadFile } from './types/UploadFile'
|
|
||||||
|
|
||||||
const requestRetryLimit = 5
|
|
||||||
|
|
||||||
export class FileUploader {
|
|
||||||
constructor(
|
|
||||||
private appLoc: string,
|
|
||||||
private serverUrl: string,
|
|
||||||
private jobsPath: string,
|
|
||||||
private setCsrfTokenWeb: any,
|
|
||||||
private csrfToken: CsrfToken | null = null
|
|
||||||
) {
|
|
||||||
if (serverUrl) isUrl(serverUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
private retryCount = 0
|
|
||||||
|
|
||||||
public uploadFile(sasJob: string, files: UploadFile[], params: any) {
|
|
||||||
if (files?.length < 1) throw new Error('Atleast one file must be provided')
|
|
||||||
|
|
||||||
let paramsString = ''
|
|
||||||
|
|
||||||
for (let param in params) {
|
|
||||||
if (params.hasOwnProperty(param)) {
|
|
||||||
paramsString += `&${param}=${params[param]}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const program = this.appLoc
|
|
||||||
? this.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
|
|
||||||
: sasJob
|
|
||||||
const uploadUrl = `${this.serverUrl}${this.jobsPath}/?${
|
|
||||||
'_program=' + program
|
|
||||||
}${paramsString}`
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
'cache-control': 'no-cache'
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const formData = new FormData()
|
|
||||||
|
|
||||||
for (let file of files) {
|
|
||||||
formData.append('file', file.file, file.fileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.csrfToken) formData.append('_csrf', this.csrfToken.value)
|
|
||||||
|
|
||||||
fetch(uploadUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
referrerPolicy: 'same-origin',
|
|
||||||
headers
|
|
||||||
})
|
|
||||||
.then(async (response) => {
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 403) {
|
|
||||||
const tokenHeader = response.headers.get('X-CSRF-HEADER')
|
|
||||||
|
|
||||||
if (tokenHeader) {
|
|
||||||
const token = response.headers.get(tokenHeader)
|
|
||||||
this.csrfToken = {
|
|
||||||
headerName: tokenHeader,
|
|
||||||
value: token || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setCsrfTokenWeb(this.csrfToken)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.text()
|
|
||||||
})
|
|
||||||
.then((responseText) => {
|
|
||||||
if (isLogInRequired(responseText))
|
|
||||||
reject('You must be logged in to upload a fle')
|
|
||||||
|
|
||||||
if (needsRetry(responseText)) {
|
|
||||||
if (this.retryCount < requestRetryLimit) {
|
|
||||||
this.retryCount++
|
|
||||||
this.uploadFile(sasJob, files, params).then(
|
|
||||||
(res: any) => resolve(res),
|
|
||||||
(err: any) => reject(err)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.retryCount = 0
|
|
||||||
reject(responseText)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.retryCount = 0
|
|
||||||
|
|
||||||
try {
|
|
||||||
resolve(JSON.parse(responseText))
|
|
||||||
} catch (e) {
|
|
||||||
reject(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +1,51 @@
|
|||||||
import { isUrl } from './utils'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A client for interfacing with the SAS9 REST API
|
* A client for interfacing with the SAS9 REST API
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export class SAS9ApiClient {
|
export class SAS9ApiClient {
|
||||||
constructor(private serverUrl: string) {
|
constructor(private serverUrl: string) {}
|
||||||
if (serverUrl) isUrl(serverUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns on object containing the server URL
|
* @returns an object containing the server URL
|
||||||
*/
|
*/
|
||||||
public getConfig() {
|
public getConfig() {
|
||||||
return {
|
return {
|
||||||
serverUrl: this.serverUrl
|
serverUrl: this.serverUrl,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates serverurl which is not null
|
* Updates serverUrl which is not null
|
||||||
* @param serverUrl - the URL of the server.
|
* @param serverUrl - the URL of the server.
|
||||||
*/
|
*/
|
||||||
public setConfig(serverUrl: string) {
|
public setConfig(serverUrl: string) {
|
||||||
if (serverUrl) this.serverUrl = serverUrl
|
if (serverUrl) this.serverUrl = serverUrl
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes code on a SAS9 server.
|
|
||||||
* @param linesOfCode - an array of lines of code to execute
|
|
||||||
* @param serverName - the server to execute the code on
|
|
||||||
* @param repositoryName - the repository to execute the code on
|
|
||||||
*/
|
|
||||||
public async executeScript(
|
|
||||||
linesOfCode: string[],
|
|
||||||
serverName: string,
|
|
||||||
repositoryName: string
|
|
||||||
) {
|
|
||||||
const requestPayload = linesOfCode.join('\n')
|
|
||||||
const executeScriptRequest = {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json'
|
|
||||||
},
|
|
||||||
body: `command=${requestPayload}`
|
|
||||||
}
|
}
|
||||||
const executeScriptResponse = await fetch(
|
|
||||||
`${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`,
|
|
||||||
executeScriptRequest
|
|
||||||
).then((res) => res.text())
|
|
||||||
|
|
||||||
return executeScriptResponse
|
/**
|
||||||
}
|
* Executes code on a SAS9 server.
|
||||||
|
* @param linesOfCode - an array of lines of code to execute
|
||||||
|
* @param serverName - the server to execute the code on
|
||||||
|
* @param repositoryName - the repository to execute the code on
|
||||||
|
*/
|
||||||
|
public async executeScript(
|
||||||
|
linesOfCode: string[], // FIXME: rename
|
||||||
|
serverName: string,
|
||||||
|
repositoryName: string
|
||||||
|
) {
|
||||||
|
const requestPayload = linesOfCode.join('\n')
|
||||||
|
const executeScriptRequest = {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {Accept: 'application/json'},
|
||||||
|
body: `command=${requestPayload}`,
|
||||||
|
}
|
||||||
|
// FIXME: use axios instead of fetch
|
||||||
|
const executeScriptResponse = await fetch(
|
||||||
|
`${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`,
|
||||||
|
executeScriptRequest
|
||||||
|
).then((res) => res.text())
|
||||||
|
// FIXME: no catch block
|
||||||
|
|
||||||
|
return executeScriptResponse
|
||||||
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,36 +1,37 @@
|
|||||||
import SASjs from './index'
|
import SASjs from "./index";
|
||||||
|
|
||||||
const adapter = new SASjs()
|
const adapter = new SASjs();
|
||||||
|
|
||||||
it('should parse SAS9 source code', async (done) => {
|
// FIXME: adapter doesn't have 'parseSAS9SourceCode' and 'parseGeneratedCode'
|
||||||
expect(sampleResponse).toBeTruthy()
|
it("should parse SAS9 source code", async done => {
|
||||||
const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse)
|
expect(sampleResponse).toBeTruthy();
|
||||||
expect(parsedSourceCode).toBeTruthy()
|
const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse);
|
||||||
const sourceCodeLines = parsedSourceCode.split('\r\n')
|
expect(parsedSourceCode).toBeTruthy();
|
||||||
expect(sourceCodeLines.length).toEqual(5)
|
const sourceCodeLines = parsedSourceCode.split("\r\n");
|
||||||
expect(sourceCodeLines[0].startsWith('6')).toBeTruthy()
|
expect(sourceCodeLines.length).toEqual(5);
|
||||||
expect(sourceCodeLines[1].startsWith('7')).toBeTruthy()
|
expect(sourceCodeLines[0].startsWith("6")).toBeTruthy();
|
||||||
expect(sourceCodeLines[2].startsWith('8')).toBeTruthy()
|
expect(sourceCodeLines[1].startsWith("7")).toBeTruthy();
|
||||||
expect(sourceCodeLines[3].startsWith('9')).toBeTruthy()
|
expect(sourceCodeLines[2].startsWith("8")).toBeTruthy();
|
||||||
expect(sourceCodeLines[4].startsWith('10')).toBeTruthy()
|
expect(sourceCodeLines[3].startsWith("9")).toBeTruthy();
|
||||||
done()
|
expect(sourceCodeLines[4].startsWith("10")).toBeTruthy();
|
||||||
})
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
it('should parse generated code', async (done) => {
|
it("should parse generated code", async done => {
|
||||||
expect(sampleResponse).toBeTruthy()
|
expect(sampleResponse).toBeTruthy();
|
||||||
const parsedGeneratedCode = (adapter as any).parseGeneratedCode(
|
const parsedGeneratedCode = (adapter as any).parseGeneratedCode(
|
||||||
sampleResponse
|
sampleResponse
|
||||||
)
|
);
|
||||||
expect(parsedGeneratedCode).toBeTruthy()
|
expect(parsedGeneratedCode).toBeTruthy();
|
||||||
const generatedCodeLines = parsedGeneratedCode.split('\r\n')
|
const generatedCodeLines = parsedGeneratedCode.split("\r\n");
|
||||||
expect(generatedCodeLines.length).toEqual(5)
|
expect(generatedCodeLines.length).toEqual(5);
|
||||||
expect(generatedCodeLines[0].startsWith('MPRINT(MM_WEBIN)')).toBeTruthy()
|
expect(generatedCodeLines[0].startsWith("MPRINT(MM_WEBIN)")).toBeTruthy();
|
||||||
expect(generatedCodeLines[1].startsWith('MPRINT(MM_WEBLEFT)')).toBeTruthy()
|
expect(generatedCodeLines[1].startsWith("MPRINT(MM_WEBLEFT)")).toBeTruthy();
|
||||||
expect(generatedCodeLines[2].startsWith('MPRINT(MM_WEBOUT)')).toBeTruthy()
|
expect(generatedCodeLines[2].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy();
|
||||||
expect(generatedCodeLines[3].startsWith('MPRINT(MM_WEBRIGHT)')).toBeTruthy()
|
expect(generatedCodeLines[3].startsWith("MPRINT(MM_WEBRIGHT)")).toBeTruthy();
|
||||||
expect(generatedCodeLines[4].startsWith('MPRINT(MM_WEBOUT)')).toBeTruthy()
|
expect(generatedCodeLines[4].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy();
|
||||||
done()
|
done();
|
||||||
})
|
});
|
||||||
|
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
const sampleResponse = `<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/>
|
const sampleResponse = `<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/>
|
||||||
@@ -44,5 +45,5 @@ MPRINT(MM_WEBLEFT): filename _temp temp lrecl=999999;
|
|||||||
MPRINT(MM_WEBOUT): data _null_;
|
MPRINT(MM_WEBOUT): data _null_;
|
||||||
MPRINT(MM_WEBRIGHT): file _temp;
|
MPRINT(MM_WEBRIGHT): file _temp;
|
||||||
MPRINT(MM_WEBOUT): if upcase(symget('_debug'))='LOG' then put '>>weboutBEGIN<<';
|
MPRINT(MM_WEBOUT): if upcase(symget('_debug'))='LOG' then put '>>weboutBEGIN<<';
|
||||||
`
|
`;
|
||||||
/* tslint:enable */
|
/* tslint:enable */
|
||||||
|
|||||||
1258
src/SASjs.ts
1258
src/SASjs.ts
File diff suppressed because it is too large
Load Diff
@@ -1,173 +0,0 @@
|
|||||||
import { Session, Context, CsrfToken } from './types'
|
|
||||||
import { asyncForEach, makeRequest, isUrl } from './utils'
|
|
||||||
|
|
||||||
const MAX_SESSION_COUNT = 1
|
|
||||||
|
|
||||||
export class SessionManager {
|
|
||||||
constructor(
|
|
||||||
private serverUrl: string,
|
|
||||||
private contextName: string,
|
|
||||||
private setCsrfToken: (csrfToken: CsrfToken) => void
|
|
||||||
) {
|
|
||||||
if (serverUrl) isUrl(serverUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
private sessions: Session[] = []
|
|
||||||
private currentContext: Context | null = null
|
|
||||||
private csrfToken: CsrfToken | null = null
|
|
||||||
|
|
||||||
async getSession(accessToken?: string) {
|
|
||||||
await this.createSessions(accessToken)
|
|
||||||
this.createAndWaitForSession(accessToken)
|
|
||||||
const session = this.sessions.pop()
|
|
||||||
const secondsSinceSessionCreation =
|
|
||||||
(new Date().getTime() - new Date(session!.creationTimeStamp).getTime()) /
|
|
||||||
1000
|
|
||||||
if (
|
|
||||||
secondsSinceSessionCreation >= session!.attributes.sessionInactiveTimeout
|
|
||||||
) {
|
|
||||||
await this.createSessions(accessToken)
|
|
||||||
const freshSession = this.sessions.pop()
|
|
||||||
return freshSession
|
|
||||||
}
|
|
||||||
return session
|
|
||||||
}
|
|
||||||
|
|
||||||
async clearSession(id: string, accessToken?: string) {
|
|
||||||
const deleteSessionRequest = {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: this.getHeaders(accessToken)
|
|
||||||
}
|
|
||||||
return await this.request<Session>(
|
|
||||||
`${this.serverUrl}/compute/sessions/${id}`,
|
|
||||||
deleteSessionRequest
|
|
||||||
).then(() => {
|
|
||||||
this.sessions = this.sessions.filter((s) => s.id !== id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createSessions(accessToken?: string) {
|
|
||||||
if (!this.sessions.length) {
|
|
||||||
if (!this.currentContext) {
|
|
||||||
await this.setCurrentContext(accessToken)
|
|
||||||
}
|
|
||||||
await asyncForEach(new Array(MAX_SESSION_COUNT), async () => {
|
|
||||||
const createdSession = await this.createAndWaitForSession(accessToken)
|
|
||||||
this.sessions.push(createdSession)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createAndWaitForSession(accessToken?: string) {
|
|
||||||
const createSessionRequest = {
|
|
||||||
method: 'POST',
|
|
||||||
headers: this.getHeaders(accessToken)
|
|
||||||
}
|
|
||||||
const { result: createdSession, etag } = await this.request<Session>(
|
|
||||||
`${this.serverUrl}/compute/contexts/${this.currentContext!.id}/sessions`,
|
|
||||||
createSessionRequest
|
|
||||||
)
|
|
||||||
|
|
||||||
await this.waitForSession(createdSession, etag)
|
|
||||||
this.sessions.push(createdSession)
|
|
||||||
return createdSession
|
|
||||||
}
|
|
||||||
|
|
||||||
private async setCurrentContext(accessToken?: string) {
|
|
||||||
if (!this.currentContext) {
|
|
||||||
const { result: contexts } = await this.request<{
|
|
||||||
items: Context[]
|
|
||||||
}>(`${this.serverUrl}/compute/contexts`, {
|
|
||||||
headers: this.getHeaders(accessToken)
|
|
||||||
})
|
|
||||||
|
|
||||||
const contextsList =
|
|
||||||
contexts && contexts.items && contexts.items.length
|
|
||||||
? contexts.items
|
|
||||||
: []
|
|
||||||
|
|
||||||
const currentContext = contextsList.find(
|
|
||||||
(c: any) => c.name === this.contextName
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!currentContext) {
|
|
||||||
throw new Error(
|
|
||||||
`The context ${this.contextName} was not found on the server ${this.serverUrl}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentContext = currentContext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getHeaders(accessToken?: string) {
|
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
if (accessToken) {
|
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return headers
|
|
||||||
}
|
|
||||||
|
|
||||||
private async waitForSession(
|
|
||||||
session: Session,
|
|
||||||
etag: string | null,
|
|
||||||
accessToken?: string,
|
|
||||||
silent = false
|
|
||||||
) {
|
|
||||||
let sessionState = session.state
|
|
||||||
const headers: any = {
|
|
||||||
...this.getHeaders(accessToken),
|
|
||||||
'If-None-Match': etag
|
|
||||||
}
|
|
||||||
const stateLink = session.links.find((l: any) => l.rel === 'state')
|
|
||||||
return new Promise(async (resolve, _) => {
|
|
||||||
if (sessionState === 'pending') {
|
|
||||||
if (stateLink) {
|
|
||||||
if (!silent) {
|
|
||||||
console.log('Polling session status... \n')
|
|
||||||
}
|
|
||||||
const { result: state } = await this.request<string>(
|
|
||||||
`${this.serverUrl}${stateLink.href}?wait=30`,
|
|
||||||
{
|
|
||||||
headers
|
|
||||||
},
|
|
||||||
'text'
|
|
||||||
)
|
|
||||||
|
|
||||||
sessionState = state.trim()
|
|
||||||
if (!silent) {
|
|
||||||
console.log(`Current state: ${sessionState}\n`)
|
|
||||||
}
|
|
||||||
resolve(sessionState)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resolve(sessionState)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private async request<T>(
|
|
||||||
url: string,
|
|
||||||
options: RequestInit,
|
|
||||||
contentType: 'text' | 'json' = 'json'
|
|
||||||
) {
|
|
||||||
if (this.csrfToken) {
|
|
||||||
options.headers = {
|
|
||||||
...options.headers,
|
|
||||||
[this.csrfToken.headerName]: this.csrfToken.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return await makeRequest<T>(
|
|
||||||
url,
|
|
||||||
options,
|
|
||||||
(token) => {
|
|
||||||
this.csrfToken = token
|
|
||||||
this.setCsrfToken(token)
|
|
||||||
},
|
|
||||||
contentType
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import SASjs from './SASjs'
|
import SASjs from './SASjs'
|
||||||
|
|
||||||
export * from './types'
|
export * from './types'
|
||||||
export * from './SASViyaApiClient'
|
export * from './SASViyaApiClient'
|
||||||
export * from './SAS9ApiClient'
|
export * from './SAS9ApiClient'
|
||||||
|
|
||||||
export default SASjs
|
export default SASjs
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
export interface Context {
|
export interface Context {
|
||||||
name: string
|
name: string;
|
||||||
id: string
|
id: string;
|
||||||
createdBy: string
|
createdBy: string;
|
||||||
version: number
|
version: number;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export interface CsrfToken {
|
export interface CsrfToken {
|
||||||
headerName: string
|
headerName: string;
|
||||||
value: string
|
value: string;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Link } from './Link'
|
import { Link } from "./Link";
|
||||||
|
|
||||||
export interface Folder {
|
export interface Folder {
|
||||||
id: string
|
id: string;
|
||||||
uri: string
|
uri: string;
|
||||||
links: Link[]
|
links: Link[];
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import { Link } from './Link'
|
import { Link } from "./Link";
|
||||||
import { JobResult } from './JobResult'
|
import { JobResult } from "./JobResult";
|
||||||
|
|
||||||
export interface Job {
|
export interface Job {
|
||||||
id: string
|
id: string;
|
||||||
name: string
|
name: string;
|
||||||
uri: string
|
uri: string;
|
||||||
createdBy: string
|
createdBy: string;
|
||||||
code?: string
|
links: Link[];
|
||||||
links: Link[]
|
results: JobResult;
|
||||||
results: JobResult
|
|
||||||
error?: any
|
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user