Browse Source

Merge branch 'master' of https://github.com/Squidex/squidex into rule-email

pull/340/head
seamys 8 years ago
parent
commit
959321effc
  1. 11
      Dockerfile
  2. 12
      Dockerfile.build
  3. 3
      src/Squidex/AppServices.cs
  4. 21
      src/Squidex/Pipeline/CommandMiddlewares/ETagCommandMiddleware.cs
  5. 33
      src/Squidex/Pipeline/ETagFilter.cs
  6. 14
      src/Squidex/Pipeline/ETagOptions.cs
  7. 10
      src/Squidex/app-config/karma-test-shim.js
  8. 12
      src/Squidex/app-config/karma.conf.js
  9. 23
      src/Squidex/app-config/karma.coverage.conf.js
  10. 41
      src/Squidex/app-config/tslint/linter.js
  11. 157
      src/Squidex/app-config/tslint/plugin.js
  12. 6
      src/Squidex/app-config/webpack.config.js
  13. 3
      src/Squidex/app-config/webpack.run.base.js
  14. 6
      src/Squidex/app-config/webpack.run.dev.js
  15. 10
      src/Squidex/app-config/webpack.run.prod.js
  16. 13
      src/Squidex/app-config/webpack.test.js
  17. 2
      src/Squidex/app/features/content/shared/content-item.component.html
  18. 2
      src/Squidex/app/features/content/shared/field-editor.component.html
  19. 2
      src/Squidex/app/framework/angular/forms/toggle.component.html
  20. 19
      src/Squidex/app/framework/angular/forms/toggle.component.ts
  21. 2
      src/Squidex/app/shared/components/language-selector.component.html
  22. 3
      src/Squidex/app/shared/components/language-selector.component.scss
  23. 7
      src/Squidex/appsettings.json
  24. 5117
      src/Squidex/package-lock.json
  25. 31
      src/Squidex/package.json
  26. 10
      src/Squidex/wwwroot/scripts/editor-sdk.js
  27. 21
      tests/Squidex.Tests/Pipeline/CommandMiddlewares/ETagCommandMiddlewareTests.cs
  28. 88
      tests/Squidex.Tests/Pipeline/ETagFilterTests.cs

11
Dockerfile

@ -3,20 +3,19 @@
#
FROM squidex/dotnet:2.2-sdk-chromium-phantomjs-node as builder
WORKDIR /src
COPY src/Squidex/package*.json /tmp/
# Install Node packages
RUN cd /tmp && npm install
RUN cd /tmp && npm install --loglevel=error
COPY . .
WORKDIR /
# Build Frontend
RUN cp -a /tmp/node_modules /src/Squidex/ \
&& cd /src/Squidex \
RUN cp -a /tmp/node_modules src/Squidex/ \
&& cd src/Squidex \
&& npm run test:coverage \
&& npm run build:copy \
&& npm run build
# Test Backend

12
Dockerfile.build

@ -1,20 +1,18 @@
FROM squidex/dotnet:2.2-sdk-chromium-phantomjs-node as builder
WORKDIR /src
COPY src/Squidex/package*.json /tmp/
RUN cd /tmp \
&& npm install \
&& npm rebuild node-sass
# Install Node packages
RUN cd /tmp && npm install --loglevel=error
COPY . .
WORKDIR /
# Build Frontend
RUN cp -a /tmp/node_modules /src/Squidex/ \
RUN cp -a /tmp/node_modules src/Squidex/ \
&& cd /src/Squidex \
&& npm run test:coverage \
&& npm run build:copy \
&& npm run build
# Test Backend

3
src/Squidex/AppServices.cs

@ -20,6 +20,7 @@ using Squidex.Extensions.Actions.Email;
using Squidex.Extensions.Actions.Twitter;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Diagnostics;
using Squidex.Pipeline;
using Squidex.Pipeline.Robots;
namespace Squidex
@ -63,6 +64,8 @@ namespace Squidex
config.GetSection("robots"));
services.Configure<GCHealthCheckOptions>(
config.GetSection("healthz:gc"));
services.Configure<ETagOptions>(
config.GetSection("etags"));
services.Configure<MyContentsControllerOptions>(
config.GetSection("contentsController"));

21
src/Squidex/Pipeline/CommandMiddlewares/ETagCommandMiddleware.cs

@ -33,16 +33,23 @@ namespace Squidex.Pipeline.CommandMiddlewares
return;
}
context.Command.ExpectedVersion = EtagVersion.Any;
var headers = httpContextAccessor.HttpContext.Request.Headers;
var headerMatch = headers[HeaderNames.IfMatch].ToString();
if (!string.IsNullOrWhiteSpace(headerMatch) && long.TryParse(headerMatch, NumberStyles.Any, CultureInfo.InvariantCulture, out var expectedVersion))
{
context.Command.ExpectedVersion = expectedVersion;
}
else
if (headers.TryGetValue(HeaderNames.IfMatch, out var etag) && !string.IsNullOrWhiteSpace(etag))
{
context.Command.ExpectedVersion = EtagVersion.Any;
var etagValue = etag.ToString();
if (etagValue.StartsWith("W/", StringComparison.OrdinalIgnoreCase))
{
etagValue = etagValue.Substring(2);
}
if (long.TryParse(etagValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var expectedVersion))
{
context.Command.ExpectedVersion = expectedVersion;
}
}
await next();

33
src/Squidex/Pipeline/ETagFilter.cs

@ -9,27 +9,44 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
namespace Squidex.Pipeline
{
public sealed class ETagFilter : IAsyncActionFilter
{
private readonly ETagOptions options;
public ETagFilter(IOptions<ETagOptions> options)
{
this.options = options.Value;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var resultContext = await next();
var httpContext = context.HttpContext;
if (HttpMethods.IsGet(httpContext.Request.Method) &&
httpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var noneMatch) &&
httpContext.Response.StatusCode == 200 &&
httpContext.Response.Headers.TryGetValue(HeaderNames.ETag, out var etag) &&
!string.IsNullOrWhiteSpace(noneMatch) &&
!string.IsNullOrWhiteSpace(etag) &&
string.Equals(etag, noneMatch, System.StringComparison.Ordinal))
if (httpContext.Response.Headers.TryGetValue(HeaderNames.ETag, out var etag) && !string.IsNullOrWhiteSpace(etag))
{
resultContext.Result = new StatusCodeResult(304);
string etagValue = etag;
if (!options.Strong)
{
etagValue = "W/" + etag;
httpContext.Response.Headers[HeaderNames.ETag] = etagValue;
}
if (HttpMethods.IsGet(httpContext.Request.Method) &&
httpContext.Response.StatusCode == 200 &&
httpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var noneMatch) && !string.IsNullOrWhiteSpace(noneMatch) &&
string.Equals(etagValue, noneMatch, System.StringComparison.Ordinal))
{
resultContext.Result = new StatusCodeResult(304);
}
}
}
}

14
src/Squidex/Pipeline/ETagOptions.cs

@ -0,0 +1,14 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Pipeline
{
public sealed class ETagOptions
{
public bool Strong { get; set; }
}
}

10
src/Squidex/app-config/karma-test-shim.js

@ -21,14 +21,16 @@ testing.TestBed.initTestEnvironment(
var testContext = require.context('../app', true, /\.spec\.ts/);
/*
* get all the files, for each file, call the context function
/**
* Get all the files, for each file, call the context function
* that will require the file and load it up here. Context will
* loop and require those spec files here
* loop and require those spec files here.
*/
function requireAll(requireContext) {
return requireContext.keys().map(requireContext);
}
// requires and returns all modules that match
/**
* Requires and returns all modules that match.
*/
var modules = requireAll(testContext);

12
src/Squidex/app-config/karma.conf.js

@ -3,14 +3,14 @@
module.exports = function (config) {
var _config = {
/**
* Base path that will be used to resolve all patterns (e.g. files, exclude)
* Base path that will be used to resolve all patterns (e.g. files, exclude).
*/
basePath: '',
frameworks: ['jasmine'],
/**
* Load additional test shim to setup angular2 for testing
* Load additional test shim to setup angular2 for testing.
*/
files: [
{ pattern: './app-config/karma-test-shim.js', watched: false }
@ -33,21 +33,21 @@ module.exports = function (config) {
noInfo: true
},
/*
* Leave Jasmine Spec Runner output visible in browser
/**
* Leave Jasmine Spec Runner output visible in browser.
*/
client: {
clearContext: false
},
/*
/**
* Use a mocha style console reporter and html reporter.
*/
reporters: ['kjhtml', 'mocha'],
/**
* Run with chrome to enable debugging
* Run with chrome to enable debugging.
*
* available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
*/

23
src/Squidex/app-config/karma.coverage.conf.js

@ -3,14 +3,14 @@
module.exports = function (config) {
var _config = {
/**
* Base path that will be used to resolve all patterns (e.g. files, exclude)
* Base path that will be used to resolve all patterns (e.g. files, exclude).
*/
basePath: '',
frameworks: ['jasmine'],
/**
* Load additional test shim to setup angular2 for testing
* Load additional test shim to setup angular2 for testing.
*/
files: [
{ pattern: './app-config/karma-test-shim.js', watched: false }
@ -33,21 +33,20 @@ module.exports = function (config) {
noInfo: true
},
/*
* Use a mocha style console reporter, html reporter and the code coverage reporter
/**
* Use a mocha style console reporter, html reporter and the code coverage reporter.
*/
reporters: ['mocha', 'html', 'coverage-istanbul'],
// HtmlReporter configuration
htmlReporter: {
useCompactStyle: true,
/**
* Use the same folder like the html report for coverage reports
* Use the same folder like the html report for coverage reports.
*/
outputFile: '_test-output/tests.html',
/**
* Group the output by test suite (describe), equivalent to mocha reporter
* Group the output by test suite (describe), equivalent to mocha reporter.
*/
groupSuites: true
},
@ -64,22 +63,24 @@ module.exports = function (config) {
},
/**
* Disable continuous Integration mode, run only one time
* Disable continuous Integration mode, run only one time.
*/
singleRun: true,
customLaunchers: {
ChromeCustom: {
base: 'ChromeHeadless',
// We must disable the Chrome sandbox (Chrome's sandbox needs more permissions than Docker allows by default)
/**
* We must disable the Chrome sandbox (Chrome's sandbox needs more permissions than Docker allows by default).
*/
flags: ['--no-sandbox']
}
},
/**
* Run with chrome because phantom js does not provide all types, e.g. DragEvent
* Run with chrome because phantom js does not provide all types.
*
* available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
* Available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
*/
browsers: ['ChromeCustom']
};

41
src/Squidex/app-config/tslint/linter.js

@ -1,41 +0,0 @@
const { Runner, run } = require('tslint/lib/runner');
const options = JSON.parse(process.argv[2]) || {};
function logToParent(message) {
process.stdout.write(message);
}
function runLinter(runnerOptions, write) {
const logTsLint = message => {
write('tslint:' + message)
};
if (run) {
const logger = { log: logTsLint, error: logTsLint };
return run(runnerOptions, logger);
} else if (Runner) {
return new Promise(resolve => {
new Runner(runnerOptions, { write: logTsLint }).run(resolve);
});
} else {
write('tsinfo:Unable to launch tslint. No suitable runner found.');
}
}
logToParent('tsinfo:Linting started in separate process...');
const runnerOptions = Object.assign({
exclude: [],
format: 'json'
}, options);
runLinter(runnerOptions, logToParent)
.then(() => {
logToParent('tsinfo:Linting complete.');
process.exit();
}).catch(error => {
logToParent(`tserror:Error starting linter: ${error}\n${error.stack}`);
});

157
src/Squidex/app-config/tslint/plugin.js

@ -1,157 +0,0 @@
const path = require('path');
const chalk = require('chalk');
const { fork } = require('child_process');
function apply(options, compiler) {
let linterProcess;
let linterPromise;
let linterIteration = 0;
function compileHook() {
if (linterProcess && linterProcess.kill) {
// Exits any outstanding child process if one exists
linterProcess.kill();
}
let { files = [] } = options;
if (!files.length) {
process.stdout.write(chalk.yellow.bold('\n[tslint-plugin] No `files` option specified.\n'));
return;
}
options.files = Array.isArray(files) ? files : [files];
// Spawn a child process to run the linter
linterProcess = fork(path.resolve(__dirname, 'linter.js'), [JSON.stringify(options)], {
silent: true
});
// Use the iteration to cancel previous promises.
linterIteration++;
linterPromise = new Promise(resolve => {
const linterOutBuffer = [];
linterProcess.stdout.on('data', (message) => {
if (message) {
const msg = message.toString();
for (let line of msg.split('\n')) {
const indexOfSeparator = line.indexOf(':');
if (indexOfSeparator > 0) {
const type = line.substring(0, indexOfSeparator);
const body = line.substring(indexOfSeparator + 1);
switch (type) {
case 'tslint': {
const json = JSON.parse(body);
for (let item of json) {
linterOutBuffer.push(item);
}
break;
}
case 'tsinfo': {
process.stdout.write(chalk.cyan(`[tslint-plugin] ${body}\n`));
break;
}
case 'tserror': {
process.stderr.write(chalk.red(`[tslint-plugin] ${body}\n`));
break;
}
default: {
process.stderr.write(msg);
}
}
} else {
process.stdout.write(line);
}
}
}
});
linterProcess.once('exit', () => {
resolve({ iteration: linterIteration, out: linterOutBuffer });
// Clean up the linterProcess when finished
delete linterProcess;
});
});
}
function createError(message) {
const error = new Error(message);
delete error.stackTrace;
return error;
}
function emitHook(compilation, callback) {
if (linterPromise && options.waitForLinting) {
linterPromise.then(result => {
for (let r of result.out) {
const msg = `${r.name}:${r.startPosition.line + 1}:${r.startPosition.character + 1} [tslint] ${r.ruleName}: ${r.failure}`;
if (r.ruleSeverity === 'ERROR' || options.warningsAsError) {
compilation.errors.push(createError(msg));
} else {
compilation.warnings.push(createError(msg));
}
}
callback();
});
} else {
callback();
}
}
function doneHook() {
const currentIteration = linterIteration;
if (linterPromise && !options.waitForLinting) {
let isResolved = false;
linterPromise.then(result => {
isResolved = true;
// If the iterations are not the same another process has already been started and we cancel these results.
if (result.iteration === currentIteration) {
for (let r of result.out) {
const msg = `${r.name}:${r.startPosition.line + 1}:${r.startPosition.character + 1} [tslint] ${r.ruleName}: ${r.failure}`;
if (r.ruleSeverity === 'ERROR' || options.warningsAsError) {
process.stderr.write(chalk.red(msg + '\n'));
} else {
process.stdout.write(chalk.yellow(msg + '\n'));
}
}
}
});
if (!isResolved) {
process.stdout.write(chalk.cyan(`[tslint-plugin] Waiting for results...\n`));
}
}
}
if (compiler.hooks) {
// Webpack 4
compiler.hooks.compile.tap('TSLintWebpackPlugin', compileHook);
compiler.hooks.emit.tapAsync('TSLintWebpackPlugin', emitHook);
compiler.hooks.done.tap('TSLintWebpackPlugin', doneHook);
} else {
// Backwards compatibility
compiler.plugin('compile', compileHook);
compiler.plugin('emit', emitHook);
compiler.plugin('done', doneHook);
}
}
module.exports = function TSLintWebpackPlugin(options = {}) {
return {
apply: apply.bind(this, options)
};
};

6
src/Squidex/app-config/webpack.config.js

@ -33,7 +33,7 @@ module.exports = {
]
},
/*
/**
* Options affecting the normal modules.
*
* See: https://webpack.js.org/configuration/module/
@ -109,7 +109,7 @@ module.exports = {
},
plugins: [
/*
/**
* Puts each bundle into a file and appends the hash of the file to the path.
*
* See: https://github.com/webpack-contrib/mini-css-extract-plugin
@ -120,7 +120,7 @@ module.exports = {
options: {
htmlLoader: {
/**
* Define the root for images, so that we can use absolute url's
* Define the root for images, so that we can use absolute urls.
*
* See: https://github.com/webpack/html-loader#Advanced_Options
*/

3
src/Squidex/app-config/webpack.run.base.js

@ -10,8 +10,7 @@ const plugins = {
module.exports = webpackMerge(commonConfig, {
/**
* The entry point for the bundle
* Our Angular.js app
* The entry point for the bundle. Our Angular app.
*
* See: https://webpack.js.org/configuration/entry-context/
*/

6
src/Squidex/app-config/webpack.run.dev.js

@ -6,7 +6,7 @@
const plugins = {
// https://github.com/jrparish/tslint-webpack-plugin
TsLintPlugin: require('./tslint/plugin')
TsLintPlugin: require('tslint-webpack-plugin')
};
module.exports = webpackMerge(runConfig, {
@ -17,7 +17,9 @@ module.exports = webpackMerge(runConfig, {
output: {
filename: '[name].js',
// Set the public path, because we are running the website from another port (5000)
/**
* Set the public path, because we are running the website from another port (5000).
*/
publicPath: 'http://localhost:3000/'
},

10
src/Squidex/app-config/webpack.run.prod.js

@ -14,7 +14,7 @@ const plugins = {
// https://github.com/NMFR/optimize-css-assets-webpack-plugin
OptimizeCSSAssetsPlugin: require("optimize-css-assets-webpack-plugin"),
// https://github.com/jrparish/tslint-webpack-plugin
TsLintPlugin: require('./tslint/plugin')
TsLintPlugin: require('tslint-webpack-plugin')
};
helpers.removeLoaders(runConfig, ['scss', 'ts']);
@ -34,15 +34,13 @@ module.exports = webpackMerge(runConfig, {
/**
* Specifies the name of each output file on disk.
* IMPORTANT: You must not specify an absolute path here!
*
* See: https://webpack.js.org/configuration/output/#output-filename
*/
filename: '[name].js',
/**
* The filename of non-entry chunks as relative path
* inside the output.path directory.
* The filename of non-entry chunks as relative path inside the output.path directory.
*
* See: https://webpack.js.org/configuration/output/#output-chunkfilename
*/
@ -63,7 +61,7 @@ module.exports = webpackMerge(runConfig, {
rules: [{
test: /\.scss$/,
/*
* Extract the content from a bundle to a file
* Extract the content from a bundle to a file.
*
* See: https://github.com/webpack-contrib/extract-text-webpack-plugin
*/
@ -75,7 +73,7 @@ module.exports = webpackMerge(runConfig, {
loader: 'sass-loader'
}],
/*
* Do not include component styles
* Do not include component styles.
*/
include: helpers.root('app', 'theme'),
}, {

13
src/Squidex/app-config/webpack.test.js

@ -1,16 +1,15 @@
 const webpack = require('webpack'),
webpackMerge = require('webpack-merge'),
path = require('path'),
helpers = require('./helpers'),
commonConfig = require('./webpack.config.js');
const webpack = require('webpack'),
webpackMerge = require('webpack-merge'),
path = require('path'),
helpers = require('./helpers'),
commonConfig = require('./webpack.config.js');
module.exports = webpackMerge(commonConfig, {
mode: 'development',
/**
* Source map for Karma from the help of karma-sourcemap-loader & karma-webpack
* Source map for Karma from the help of karma-sourcemap-loader & karma-webpack.
*
* Do not change, leave as is or it wont work.
* See: https://webpack.js.org/configuration/devtool/
*/
devtool: 'inline-source-map'

2
src/Squidex/app/features/content/shared/content-item.component.html

@ -44,7 +44,7 @@
<div *ngSwitchCase="'Boolean'">
<div [ngSwitch]="field.properties['editor']">
<div *ngSwitchCase="'Toggle'">
<sqx-toggle [formControlName]="field.name"></sqx-toggle>
<sqx-toggle [formControlName]="field.name" [threeStates]="!field.properties.isRequired"></sqx-toggle>
</div>
<div *ngSwitchCase="'Checkbox'">
<div class="form-check form-check-inline">

2
src/Squidex/app/features/content/shared/field-editor.component.html

@ -84,7 +84,7 @@
<ng-container *ngSwitchCase="'Boolean'">
<ng-container [ngSwitch]="field.properties['editor']">
<ng-container *ngSwitchCase="'Toggle'">
<sqx-toggle [formControl]="control"></sqx-toggle>
<sqx-toggle [formControl]="control" [threeStates]="!field.properties.isRequired"></sqx-toggle>
</ng-container>
<ng-container *ngSwitchCase="'Checkbox'">
<input type="checkbox" [formControl]="control" class="form-check" sqxIndeterminateValue />

2
src/Squidex/app/framework/angular/forms/toggle.component.html

@ -1,4 +1,4 @@
<div class="toggle-container" (click)="changeState()"
<div class="toggle-container" (click)="changeState($event)"
[class.disabled]="isDisabled"
[class.checked]="isChecked === true"
[class.unchecked]="isChecked === false">

19
src/Squidex/app/framework/angular/forms/toggle.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Types } from '@app/framework/internal';
@ -25,6 +25,9 @@ export class ToggleComponent implements ControlValueAccessor {
private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ };
@Input()
public threeStates = false;
public isChecked: boolean | null = null;
public isDisabled = false;
@ -53,12 +56,22 @@ export class ToggleComponent implements ControlValueAccessor {
this.callTouched = fn;
}
public changeState() {
public changeState(event: MouseEvent) {
if (this.isDisabled) {
return;
}
this.isChecked = !(this.isChecked === true);
if (this.threeStates && (event.ctrlKey || event.shiftKey)) {
if (this.isChecked) {
this.isChecked = null;
} else if (this.isChecked === null) {
this.isChecked = false;
} else {
this.isChecked = true;
}
} else {
this.isChecked = !(this.isChecked === true);
}
this.callChange(this.isChecked);
this.callTouched();

2
src/Squidex/app/shared/components/language-selector.component.html

@ -10,7 +10,7 @@
</button>
<div class="dropdown-menu" *sqxModalView="dropdown;closeAlways:true" [sqxModalTarget]="button" @fade>
<div class="dropdown-item" *ngFor="let language of languages" [class.active]="language == selectedLanguage" (click)="selectLanguage(language)">
<strong class="iso-code">{{language.iso2Code}}</strong> ({{language.englishName}})
<strong class="iso-code iso-code-dropdown">{{language.iso2Code}}</strong> ({{language.englishName}})
</div>
</div>
</div>

3
src/Squidex/app/shared/components/language-selector.component.scss

@ -7,6 +7,9 @@
.iso-code {
font-family: monospace;
}
.iso-code-dropdown {
display: inline-block;
min-width: 40px;
max-width: 60px;

7
src/Squidex/appsettings.json

@ -18,6 +18,13 @@
"enforceHttps": false
},
"etags": {
/*
* Set to true, to use strong etags.
*/
"strong": false
},
"ui": {
/*
* Regex suggestions for the UI

5117
src/Squidex/package-lock.json

File diff suppressed because it is too large

31
src/Squidex/package.json

@ -5,15 +5,14 @@
"license": "MIT",
"repository": "https://github.com/SebastianStehle/Squidex",
"scripts": {
"copy": "cpx node_modules/oidc-client/dist/oidc-client.min.js wwwroot/scripts/",
"start": "npm run copy && webpack-dev-server --config app-config/webpack.run.dev.js --inline --port 3000 --hot",
"test": "karma start",
"test:coverage": "karma start karma.coverage.conf.js",
"test:clean": "rimraf _test-output",
"dev": "cpx node_modules/oidc-client/dist/oidc-client.min.js wwwroot/scripts/ && webpack-dev-server --config app-config/webpack.run.dev.js --inline --port 3000",
"start": "cpx node_modules/oidc-client/dist/oidc-client.min.js wwwroot/scripts/ && webpack-dev-server --config app-config/webpack.run.dev.js --inline --port 3000 --hot",
"build": "webpack --config app-config/webpack.run.prod.js",
"build:copy": "cpx node_modules/oidc-client/dist/oidc-client.min.js wwwroot/scripts/",
"build:clean": "rimraf wwwroot/build",
"tslint": "tslint -c tslint.json -p tsconfig.json app/**/*.ts"
"tslint": "tslint -c tslint.json -p tsconfig.json app/**/*.ts",
"build": "npm run copy && webpack --config app-config/webpack.run.prod.js",
"build:clean": "rimraf wwwroot/build"
},
"dependencies": {
"@angular/animations": "7.1.4",
@ -27,7 +26,7 @@
"@angular/router": "7.1.4",
"angular2-chartjs": "0.5.1",
"babel-polyfill": "6.26.0",
"bootstrap": "4.1.3",
"bootstrap": "4.2.1",
"core-js": "2.6.1",
"graphiql": "0.12.0",
"graphql": "14.0.2",
@ -35,7 +34,7 @@
"mousetrap": "1.6.2",
"ng2-dnd": "5.0.2",
"ngx-color-picker": "7.2.0",
"oidc-client": "1.6.0",
"oidc-client": "1.6.1",
"pikaday": "1.8.0",
"progressbar.js": "1.0.1",
"react": "16.7.0",
@ -51,19 +50,19 @@
"@angular/compiler-cli": "7.1.4",
"@ngtools/webpack": "7.1.4",
"@types/core-js": "2.5.0",
"@types/jasmine": "3.3.4",
"@types/jasmine": "3.3.5",
"@types/mousetrap": "1.6",
"@types/node": "10.12.18",
"@types/react": "16.7.17",
"@types/react": "16.7.18",
"@types/react-dom": "16.0.11",
"@types/sortablejs": "1.7.1",
"@types/sortablejs": "1.7.2",
"angular-router-loader": "0.8.5",
"angular2-template-loader": "0.6.2",
"awesome-typescript-loader": "5.2.1",
"babel-core": "6.26.3",
"codelyzer": "4.5.0",
"cpx": "1.5.0",
"css-loader": "2.0.1",
"css-loader": "2.1.0",
"file-loader": "3.0.1",
"html-loader": "0.5.5",
"html-webpack-plugin": "3.2.0",
@ -91,14 +90,14 @@
"style-loader": "0.23.1",
"tsconfig-paths-webpack-plugin": "3.2.0",
"tslint": "5.12.0",
"tslint-webpack-plugin": "1.3.0",
"tslint-webpack-plugin": "2.0.0",
"typemoq": "2.1.0",
"typescript": "3.1.1",
"uglifyjs-webpack-plugin": "2.0.1",
"uglifyjs-webpack-plugin": "2.1.1",
"underscore": "1.9.1",
"webpack": "4.28.1",
"webpack": "4.28.2",
"webpack-cli": "3.1.2",
"webpack-dev-server": "3.1.10",
"webpack-dev-server": "3.1.14",
"webpack-merge": "4.1.5"
}
}

10
src/Squidex/wwwroot/scripts/editor-sdk.js

@ -44,7 +44,7 @@ function SquidexFormField() {
}, 500);
var editor = {
/*
/**
* Notifies the control container that the editor has been touched.
*/
touched: function () {
@ -53,7 +53,7 @@ function SquidexFormField() {
}
},
/*
/**
* Notifies the control container that the value has been changed.
*/
valueChanged: function (value) {
@ -62,7 +62,7 @@ function SquidexFormField() {
}
},
/*
/**
* Register the disabled handler.
*/
onDisabled: function (callback) {
@ -73,7 +73,7 @@ function SquidexFormField() {
}
},
/*
/**
* Register the disabled handler.
*/
onValueChanged: function (callback) {
@ -84,7 +84,7 @@ function SquidexFormField() {
}
},
/*
/**
* Clean the editor SDK.
*/
clean: function () {

21
tests/Squidex.Tests/Pipeline/CommandMiddlewares/ETagCommandMiddlewareTests.cs

@ -20,13 +20,13 @@ namespace Squidex.Pipeline.CommandMiddlewares
{
private readonly IHttpContextAccessor httpContextAccessor = A.Fake<IHttpContextAccessor>();
private readonly ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly IHeaderDictionary requestHeaders = new HeaderDictionary();
private readonly HttpContext httpContext = new DefaultHttpContext();
private readonly ETagCommandMiddleware sut;
public ETagCommandMiddlewareTests()
{
A.CallTo(() => httpContextAccessor.HttpContext.Request.Headers)
.Returns(requestHeaders);
A.CallTo(() => httpContextAccessor.HttpContext)
.Returns(httpContext);
sut = new ETagCommandMiddleware(httpContextAccessor);
}
@ -48,7 +48,20 @@ namespace Squidex.Pipeline.CommandMiddlewares
[Fact]
public async Task Should_add_expected_version_to_command()
{
requestHeaders["If-Match"] = "13";
httpContext.Request.Headers[HeaderNames.IfMatch] = "13";
var command = new CreateContent();
var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context);
Assert.Equal(13, context.Command.ExpectedVersion);
}
[Fact]
public async Task Should_add_weak_etag_as_expected_version_to_command()
{
httpContext.Request.Headers[HeaderNames.IfMatch] = "W/13";
var command = new CreateContent();
var context = new CommandContext(command, commandBus);

88
tests/Squidex.Tests/Pipeline/ETagFilterTests.cs

@ -0,0 +1,88 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using FakeItEasy;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Xunit;
namespace Squidex.Pipeline
{
public class ETagFilterTests
{
private readonly HttpContext httpContext = new DefaultHttpContext();
private readonly ActionExecutingContext executingContext;
private readonly ActionExecutedContext executedContext;
private readonly ETagFilter sut = new ETagFilter(Options.Create(new ETagOptions()));
public ETagFilterTests()
{
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var filters = new List<IFilterMetadata>();
executingContext = new ActionExecutingContext(actionContext, filters, new Dictionary<string, object>(), this);
executedContext = new ActionExecutedContext(actionContext, filters, this)
{
Result = new OkResult()
};
}
[Fact]
public async Task Should_convert_strong_to_weak_tag()
{
httpContext.Response.Headers[HeaderNames.ETag] = "13";
await sut.OnActionExecutionAsync(executingContext, () => Task.FromResult(executedContext));
Assert.Equal("W/13", httpContext.Response.Headers[HeaderNames.ETag]);
}
[Fact]
public async Task Should_not_convert_empty_strong_to_weak_tag()
{
httpContext.Response.Headers[HeaderNames.ETag] = string.Empty;
await sut.OnActionExecutionAsync(executingContext, () => Task.FromResult(executedContext));
Assert.Null((string)httpContext.Response.Headers[HeaderNames.ETag]);
}
[Fact]
public async Task Should_return_304_for_same_etags()
{
httpContext.Request.Method = HttpMethods.Get;
httpContext.Request.Headers[HeaderNames.IfNoneMatch] = "W/13";
httpContext.Response.Headers[HeaderNames.ETag] = "13";
await sut.OnActionExecutionAsync(executingContext, () => Task.FromResult(executedContext));
Assert.Equal(304, (executedContext.Result as StatusCodeResult).StatusCode);
}
[Fact]
public async Task Should_not_return_304_for_different_etags()
{
httpContext.Request.Method = HttpMethods.Get;
httpContext.Request.Headers[HeaderNames.IfNoneMatch] = "W/11";
httpContext.Response.Headers[HeaderNames.ETag] = "13";
await sut.OnActionExecutionAsync(executingContext, () => Task.FromResult(executedContext));
Assert.Equal(200, (executedContext.Result as StatusCodeResult).StatusCode);
}
}
}
Loading…
Cancel
Save