diff --git a/.github/workflows/apps_automated.yml b/.github/workflows/apps_automated.yml new file mode 100644 index 000000000..e86d7efd0 --- /dev/null +++ b/.github/workflows/apps_automated.yml @@ -0,0 +1,39 @@ +name: 'apps/automated' + +on: + push: + branches: + - master + pull_request: + workflow_dispatch: + +jobs: + test: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install Python + uses: actions/setup-python@v1 + + - name: Install NativeScript + run: | + python -m pip install --upgrade pip six + npm i -g nativescript --ignore-scripts + ns usage-reporting disable + ns error-reporting disable + ns doctor + + - name: Setup + run: npm run setup + + - name: Create Emulator + uses: rigor789/action-create-emulator@master + + - name: Test (Android) + run: node tools/scripts/run-automated.js android + + - name: Test (iOS) + if: always() # run iOS tests even if Android tests failed + run: node tools/scripts/run-automated.js ios diff --git a/apps/automated/src/globals.d.ts b/apps/automated/src/globals.d.ts new file mode 100644 index 000000000..d5ddcaec7 --- /dev/null +++ b/apps/automated/src/globals.d.ts @@ -0,0 +1 @@ +declare var __CI__; diff --git a/apps/automated/src/test-runner.ts b/apps/automated/src/test-runner.ts index 160e064cb..fd6c2f0a7 100644 --- a/apps/automated/src/test-runner.ts +++ b/apps/automated/src/test-runner.ts @@ -41,16 +41,18 @@ allTests['PLATFORM'] = platformTests; import * as fsTests from './file-system/file-system-tests'; allTests['FILE-SYSTEM'] = fsTests; -// Disabled tests as they have external dependencies -// TODO: find a way to run these tests locally, but don't run them on the CI as they are flaky -// import * as httpTests from "./http/http-tests"; -// allTests["HTTP"] = httpTests; +import * as httpTests from './http/http-tests'; +import * as xhrTests from './xhr/xhr-tests'; +import * as fetchTests from './fetch/fetch-tests'; +import * as timerTests from './timer/timer-tests'; -// import * as xhrTests from "./xhr/xhr-tests"; -// allTests["XHR"] = xhrTests; - -// import * as fetchTests from "./fetch/fetch-tests"; -// allTests["FETCH"] = fetchTests; +// don't run these on CI as they are flaky +if (!__CI__) { + allTests['HTTP'] = httpTests; + allTests['XHR'] = xhrTests; + allTests['FETCH'] = fetchTests; + allTests['TIMER'] = timerTests; +} import * as appSettingsTests from './application-settings/application-settings-tests'; allTests['APPLICATION-SETTINGS'] = appSettingsTests; @@ -70,9 +72,6 @@ allTests['VIRTUAL-ARRAY'] = virtualArrayTests; import * as observableTests from './data/observable-tests'; allTests['OBSERVABLE'] = observableTests; -import * as timerTests from './timer/timer-tests'; -allTests['TIMER'] = timerTests; - import * as animationFrameTests from './animation-frame/animation-frame'; allTests['ANIMATION-FRAME'] = animationFrameTests; diff --git a/apps/automated/src/timer/timer-tests.ts b/apps/automated/src/timer/timer-tests.ts index 4f8f10bbb..7c4852098 100644 --- a/apps/automated/src/timer/timer-tests.ts +++ b/apps/automated/src/timer/timer-tests.ts @@ -155,6 +155,7 @@ export function test_setInterval_callbackCalledDuringPeriod(done) { export function test_setInterval_callbackCalledWithExtraArgs(done) { let counter: number = 0; const rnd: number = Math.random(); + const threshold = 100; const start = TKUnit.time(); const id = timer.setInterval( @@ -163,10 +164,10 @@ export function test_setInterval_callbackCalledWithExtraArgs(done) { if (counter === 4) { const end = TKUnit.time(); timer.clearInterval(id); - done(end - start > 250 ? new Error('setInterval too slow.') : null); + done(end - start > 1000 + threshold ? new Error('setInterval too slow.') : null); } }, - 50, + 250, rnd ); } @@ -179,11 +180,11 @@ export function test_setInterval_callbackNotDelayedByBusyWork() { calls++; if (firstCall) { firstCall = false; - TKUnit.wait(0.025); + TKUnit.wait(0.125); } - }, 50); + }, 250); - TKUnit.wait(0.11); + TKUnit.wait(0.55); timer.clearInterval(id); TKUnit.assertEqual(calls, 2, 'Callback should be called multiple times with busy wait'); } diff --git a/apps/automated/src/ui/button/button-tests-native.android.ts b/apps/automated/src/ui/button/button-tests-native.android.ts index 759c16c66..d17d73298 100644 --- a/apps/automated/src/ui/button/button-tests-native.android.ts +++ b/apps/automated/src/ui/button/button-tests-native.android.ts @@ -1,53 +1,53 @@ -import * as buttonModule from '@nativescript/core/ui/button'; -import * as colorModule from '@nativescript/core/color'; -import * as utilsModule from '@nativescript/core/utils/utils'; -import * as enums from '@nativescript/core/ui/enums'; +import { Color, Button, Utils, Enums } from '@nativescript/core'; -export function getNativeText(button: buttonModule.Button): string { +export function getNativeText(button: Button): string { return button.android.getText(); } -export function getNativeTextWrap(button: buttonModule.Button): boolean { +export function getNativeTextWrap(button: Button): boolean { return (button.android).getLineCount() === 1; } -export function getNativeFontSize(button: buttonModule.Button): number { - var density = utilsModule.layout.getDisplayDensity(); +export function getNativeFontSize(button: Button): number { + let density = Utils.layout.getDisplayDensity(); return button.android.getTextSize() / density; } -export function getNativeColor(button: buttonModule.Button): colorModule.Color { - return new colorModule.Color(button.android.getTextColors().getDefaultColor()); +export function getNativeColor(button: Button): Color { + return new Color(button.android.getTextColors().getDefaultColor()); } -export function getNativeBackgroundColor(button: buttonModule.Button): colorModule.Color { - var bkg = button.android.getBackground(); - if (bkg instanceof org.nativescript.widgets.BorderDrawable) { - return new colorModule.Color((bkg).getBackgroundColor()); +export function getNativeBackgroundColor(button: Button): Color { + let bg = button.android.getBackground(); + if (bg instanceof org.nativescript.widgets.BorderDrawable) { + return new Color(bg.getBackgroundColor()); + } else if (bg instanceof android.graphics.drawable.ColorDrawable) { + console.log(bg); + return new Color(bg.getColor()); } else { - return new colorModule.Color(bkg.backgroundColor); + return new Color(bg.backgroundColor); } } -export function getNativeTextAlignment(button: buttonModule.Button): string { - var gravity = button.android.getGravity(); +export function getNativeTextAlignment(button: Button): string { + let gravity = button.android.getGravity(); if ((gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK) === android.view.Gravity.LEFT) { - return enums.TextAlignment.left; + return Enums.TextAlignment.left; } if ((gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK) === android.view.Gravity.CENTER_HORIZONTAL) { - return enums.TextAlignment.center; + return Enums.TextAlignment.center; } if ((gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK) === android.view.Gravity.RIGHT) { - return enums.TextAlignment.right; + return Enums.TextAlignment.right; } return 'unexpected value'; } -export function performNativeClick(button: buttonModule.Button): void { +export function performNativeClick(button: Button): void { button.android.performClick(); } diff --git a/apps/automated/src/ui/label/label-tests-native.android.ts b/apps/automated/src/ui/label/label-tests-native.android.ts index ee4fe6090..025c189a6 100644 --- a/apps/automated/src/ui/label/label-tests-native.android.ts +++ b/apps/automated/src/ui/label/label-tests-native.android.ts @@ -3,7 +3,7 @@ import * as enums from '@nativescript/core/ui/enums'; import * as colorModule from '@nativescript/core/color'; export function getNativeTextAlignment(label: labelModule.Label): string { - var gravity = label.android.getGravity(); + let gravity = label.android.getGravity(); if ((gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK) === android.view.Gravity.LEFT) { return enums.TextAlignment.left; @@ -21,10 +21,12 @@ export function getNativeTextAlignment(label: labelModule.Label): string { } export function getNativeBackgroundColor(label: labelModule.Label): colorModule.Color { - var bkg = label.android.getBackground(); - if (bkg instanceof org.nativescript.widgets.BorderDrawable) { - return new colorModule.Color((bkg).getBackgroundColor()); + let bg = label.android.getBackground(); + if (bg instanceof org.nativescript.widgets.BorderDrawable) { + return new colorModule.Color(bg.getBackgroundColor()); + } else if (bg instanceof android.graphics.drawable.ColorDrawable) { + return new colorModule.Color(bg.getColor()); } else { - return new colorModule.Color(bkg.backgroundColor); + return new colorModule.Color(bg.backgroundColor); } } diff --git a/apps/automated/src/ui/label/label-tests.ts b/apps/automated/src/ui/label/label-tests.ts index 0383375b5..259668ee2 100644 --- a/apps/automated/src/ui/label/label-tests.ts +++ b/apps/automated/src/ui/label/label-tests.ts @@ -330,8 +330,8 @@ export class LabelTest extends testModule.UITest { normalColor = actualColors.getDefaultColor(); TKUnit.assert(normalColor, 'Expected: ' + expColor + ', Actual: ' + normalColor); - const bkg = testLabel.android.getBackground(); - actualBackgroundColor = bkg.getBackgroundColor(); + const bg = testLabel.android.getBackground(); + actualBackgroundColor = bg['getBackgroundColor'] ? bg.getBackgroundColor() : bg.getColor(); expBackgroundColor = android.graphics.Color.parseColor(backgroundColor); TKUnit.assertEqual(actualBackgroundColor, expBackgroundColor); } else { diff --git a/apps/automated/src/ui/text-field/text-field-tests-native.android.ts b/apps/automated/src/ui/text-field/text-field-tests-native.android.ts index 55b892a24..adddb5843 100644 --- a/apps/automated/src/ui/text-field/text-field-tests-native.android.ts +++ b/apps/automated/src/ui/text-field/text-field-tests-native.android.ts @@ -1,74 +1,74 @@ -import * as textFieldModule from '@nativescript/core/ui/text-field'; -import * as colorModule from '@nativescript/core/color'; -import * as utilsModule from '@nativescript/core/utils/utils'; -import * as enums from '@nativescript/core/ui/enums'; +import { TextField, Color, Utils, Enums } from '@nativescript/core'; -export function getNativeText(textField: textFieldModule.TextField): string { +export function getNativeText(textField: TextField): string { return textField.android.getText().toString(); } -export function getNativeHint(textField: textFieldModule.TextField): string { +export function getNativeHint(textField: TextField): string { return textField.android.getHint(); } -export function getNativeSecure(textField: textFieldModule.TextField): boolean { - var inputType = textField.android.getInputType(); +export function getNativeSecure(textField: TextField): boolean { + let inputType = textField.android.getInputType(); return (inputType & android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD) === android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD || (inputType & android.text.InputType.TYPE_NUMBER_VARIATION_PASSWORD) === android.text.InputType.TYPE_NUMBER_VARIATION_PASSWORD; } -export function getNativeFontSize(textField: textFieldModule.TextField): number { - var density = utilsModule.layout.getDisplayDensity(); +export function getNativeFontSize(textField: TextField): number { + let density = Utils.layout.getDisplayDensity(); return textField.android.getTextSize() / density; } -export function getNativeColor(textField: textFieldModule.TextField): colorModule.Color { - return new colorModule.Color(textField.android.getTextColors().getDefaultColor()); +export function getNativeColor(textField: TextField): Color { + return new Color(textField.android.getTextColors().getDefaultColor()); } -export function getNativePlaceholderColor(textField: textFieldModule.TextField): colorModule.Color { - return new colorModule.Color(textField.android.getHintTextColors().getDefaultColor()); +export function getNativePlaceholderColor(textField: TextField): Color { + return new Color(textField.android.getHintTextColors().getDefaultColor()); } -export function getNativeBackgroundColor(textField: textFieldModule.TextField): colorModule.Color { - var bkg = textField.android.getBackground(); - if (bkg instanceof org.nativescript.widgets.BorderDrawable) { - return new colorModule.Color((bkg).getBackgroundColor()); +export function getNativeBackgroundColor(textField: TextField): Color { + let bg = textField.android.getBackground(); + if (bg instanceof org.nativescript.widgets.BorderDrawable) { + return new Color(bg.getBackgroundColor()); + } else if (bg instanceof android.graphics.drawable.ColorDrawable) { + console.log(bg); + return new Color(bg.getColor()); } else { - return new colorModule.Color(bkg.backgroundColor); + return new Color(bg.backgroundColor); } } -export function getNativeTextAlignment(textField: textFieldModule.TextField): string { +export function getNativeTextAlignment(textField: TextField): string { var gravity = textField.android.getGravity(); if ((gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK) === android.view.Gravity.LEFT) { - return enums.TextAlignment.left; + return Enums.TextAlignment.left; } if ((gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK) === android.view.Gravity.CENTER_HORIZONTAL) { - return enums.TextAlignment.center; + return Enums.TextAlignment.center; } if ((gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK) === android.view.Gravity.RIGHT) { - return enums.TextAlignment.right; + return Enums.TextAlignment.right; } return 'unexpected value'; } -export function getNativeFocus(textField: textFieldModule.TextField): boolean { +export function getNativeFocus(textField: TextField): boolean { // return true; } -export function typeTextNatively(textField: textFieldModule.TextField, text: string): void { +export function typeTextNatively(textField: TextField, text: string): void { textField.android.requestFocus(); textField.android.setText(text); textField.android.clearFocus(); } -export function typeTextNativelyWithReturn(textField: textFieldModule.TextField, text: string): void { +export function typeTextNativelyWithReturn(textField: TextField, text: string): void { // } diff --git a/apps/automated/src/ui/text-view/text-view-tests-native.android.ts b/apps/automated/src/ui/text-view/text-view-tests-native.android.ts index 016cdb81e..d3bb5c13e 100644 --- a/apps/automated/src/ui/text-view/text-view-tests-native.android.ts +++ b/apps/automated/src/ui/text-view/text-view-tests-native.android.ts @@ -1,13 +1,10 @@ -import * as textViewModule from '@nativescript/core/ui/text-view'; -import * as colorModule from '@nativescript/core/color'; -import * as utilsModule from '@nativescript/core/utils/utils'; -import * as enums from '@nativescript/core/ui/enums'; +import { TextView, Color, Utils, Enums } from '@nativescript/core'; -export function getNativeText(textView: textViewModule.TextView): string { +export function getNativeText(textView: TextView): string { return textView.android.getText().toString(); } -export function getNativeEditable(textView: textViewModule.TextView): boolean { +export function getNativeEditable(textView: TextView): boolean { if (textView.android.getKeyListener()) { return true; } else { @@ -15,53 +12,55 @@ export function getNativeEditable(textView: textViewModule.TextView): boolean { } } -export function getNativeHint(textView: textViewModule.TextView): string { +export function getNativeHint(textView: TextView): string { return textView.android.getHint(); } -export function getNativeFontSize(textView: textViewModule.TextView): number { - var density = utilsModule.layout.getDisplayDensity(); +export function getNativeFontSize(textView: TextView): number { + let density = Utils.layout.getDisplayDensity(); return textView.android.getTextSize() / density; } -export function getNativeColor(textView: textViewModule.TextView): colorModule.Color { - return new colorModule.Color(textView.android.getTextColors().getDefaultColor()); +export function getNativeColor(textView: TextView): Color { + return new Color(textView.android.getTextColors().getDefaultColor()); } -export function getNativeBackgroundColor(textView: textViewModule.TextView): colorModule.Color { - var bkg = textView.android.getBackground(); - if (bkg instanceof org.nativescript.widgets.BorderDrawable) { - return new colorModule.Color((bkg).getBackgroundColor()); +export function getNativeBackgroundColor(textView: TextView): Color { + let bg = textView.android.getBackground(); + if (bg instanceof org.nativescript.widgets.BorderDrawable) { + return new Color(bg.getBackgroundColor()); + } else if (bg instanceof android.graphics.drawable.ColorDrawable) { + return new Color(bg.getColor()); } else { - return new colorModule.Color(bkg.backgroundColor); + return new Color(bg.backgroundColor); } } -export function getNativeTextAlignment(textView: textViewModule.TextView): string { - var gravity = textView.android.getGravity(); +export function getNativeTextAlignment(textView: TextView): string { + let gravity = textView.android.getGravity(); if ((gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK) === android.view.Gravity.LEFT) { - return enums.TextAlignment.left; + return Enums.TextAlignment.left; } if ((gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK) === android.view.Gravity.CENTER_HORIZONTAL) { - return enums.TextAlignment.center; + return Enums.TextAlignment.center; } if ((gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK) === android.view.Gravity.RIGHT) { - return enums.TextAlignment.right; + return Enums.TextAlignment.right; } return 'unexpected value'; } -export function typeTextNatively(textView: textViewModule.TextView, text: string): void { +export function typeTextNatively(textView: TextView, text: string): void { textView.android.requestFocus(); textView.android.setText(text); textView.android.clearFocus(); } -export function getNativeMaxLines(textView: textViewModule.TextView): number { +export function getNativeMaxLines(textView: TextView): number { return textView.android.getMaxLines(); } diff --git a/apps/automated/webpack.custom.config.js b/apps/automated/webpack.custom.config.js index 40d3099b0..f13fc0393 100644 --- a/apps/automated/webpack.custom.config.js +++ b/apps/automated/webpack.custom.config.js @@ -1,6 +1,8 @@ -const webpackConfig = require('./webpack.config'); +const webpack = require('webpack'); const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpackConfig = require('./webpack.config'); + module.exports = (env) => { env = env || {}; const baseConfig = webpackConfig(env); @@ -9,5 +11,9 @@ module.exports = (env) => { { from: { glob: 'ui/web-view/*.html', dot: false } } ])) + baseConfig.plugins.push(new webpack.DefinePlugin({ + __CI__: !!process.env.CI + })) + return baseConfig; }; diff --git a/package.json b/package.json index fc3b6163a..716575868 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "reduce-css-calc": "~2.1.7", "shady-css-parser": "^0.1.0", "terser-webpack-plugin": "~3.0.6", + "tree-kill": "^1.2.2", "ts-jest": "26.4.0", "ts-node": "~8.10.2", "ts-patch": "^1.2.5", diff --git a/packages/core/ui/styling/background.android.ts b/packages/core/ui/styling/background.android.ts index 39f193465..7d1856f4f 100644 --- a/packages/core/ui/styling/background.android.ts +++ b/packages/core/ui/styling/background.android.ts @@ -45,12 +45,13 @@ export namespace ad { androidView._cachedDrawable = constantState || drawable; } const isBorderDrawable = drawable instanceof org.nativescript.widgets.BorderDrawable; + const isColorDrawable = drawable instanceof android.graphics.drawable.ColorDrawable; const onlyColor = !background.hasBorderWidth() && !background.hasBorderRadius() && !background.clipPath && !background.image && !!background.color; - if (drawable instanceof android.graphics.drawable.ColorDrawable && onlyColor) { + if (!isBorderDrawable && isColorDrawable && onlyColor) { drawable.setColor(background.color.android); drawable.invalidateSelf(); } else if (isSetColorFilterOnlyWidget(nativeView) && drawable && onlyColor) { - if (drawable instanceof org.nativescript.widgets.BorderDrawable && androidView._cachedDrawable) { + if (isBorderDrawable && androidView._cachedDrawable) { if (!(androidView._cachedDrawable instanceof android.graphics.drawable.Drawable.ConstantState)) { return; } diff --git a/tools/scripts/run-automated.js b/tools/scripts/run-automated.js new file mode 100644 index 000000000..8c67329a1 --- /dev/null +++ b/tools/scripts/run-automated.js @@ -0,0 +1,36 @@ +/** + * Script to run the automated tests & exit after the tests are finished. + * Mainly intended to be used on CI + * + * Usage: node run-automated.js + */ +const spawn = require('child_process').spawn +const kill = require('tree-kill'); + +const spawned_process = spawn('npm', ['start', `apps.automated.${process.argv[2]}`], { + stdio: ['inherit', 'pipe', 'pipe'] +}) + +const {stdout, stderr} = spawned_process + +stdout.pipe(process.stdout) +stderr.pipe(process.stderr) + +let lineBuffer = [] + +stdout.on('data', data => { + const line = data.toString(); + + // start buffering lines when tests are complete + if(lineBuffer.length || line.includes('=== ALL TESTS COMPLETE ===')) { + lineBuffer.push(line) + } + + if(line.includes('Tests EOF!')) { + let ok = lineBuffer.join('\n').includes('OK, 0 failed') + console.log(ok ? 'Tests PASSED' : 'Tests FAILED'); + kill(spawned_process.pid) + process.exit(ok ? 0 : 1) + } +}) +