diff --git a/Jenkinsfile-SmokeTest b/Jenkinsfile-SmokeTest new file mode 100644 index 00000000..b5677c09 --- /dev/null +++ b/Jenkinsfile-SmokeTest @@ -0,0 +1,131 @@ +#!groovy +@Library('waluigi@release/7') _ + +properties([ + disableConcurrentBuilds(), + buildDiscarder(logRotator( + artifactDaysToKeepStr: '', + artifactNumToKeepStr: '1', + daysToKeepStr: '', + numToKeepStr: '50' + )), + parameters([ + string(defaultValue: 'next', description: 'The NPM publish tag ', name: 'NPM_TAG', trim: false), + ]) +]) + +// Generates a custom route file that contains the next version of Angular to test against +def generateNPMVersionRouteFile(Map args = new LinkedHashMap()) { + // @angular/core's version is the one baked into the angular app + def nextVersion = sh(script: "npm view @angular/core@${args.npm_tag} version", returnStdout: true).trim() + + echo "Target framework version: ${nextVersion}" + + def routeFileContent = readJSON text: """ + [{ + "request": { + "method": "get", + "path": "/custom/integration/info" + }, + "response": { + "status": 200, + "json": { + "version": "${nextVersion}" + } + } + }] + """ + writeJSON file: "${args.filePath}", json: routeFileContent +} + +// Updates the Angular dependencies to the version specified by the npm tag +def updateDependenciesWithTag(Map args = new LinkedHashMap()) { + String npm_tag = args.npm_tag + sh "yarn add @angular/core@${npm_tag} @angular/animations@${npm_tag} @angular/common@${npm_tag} @angular/compiler@${npm_tag} @angular/core@${npm_tag} @angular/forms@${npm_tag} @angular/platform-browser@${npm_tag} @angular-devkit/build-angular@${npm_tag} @angular/cli@${npm_tag} @angular/compiler-cli@${npm_tag}" +} + +def runSmokeTests(Map args = new LinkedHashMap()) { + def platforms = args.platforms ?: [ + [ browser: "chrome", provider: "lambdatest" ] + ] + String customRouteFilePath = "${WORKSPACE}/version.json" + String additionalArgs = "--chunk 20 --totalTimeout 1800000 --singleTimeout 90000 --retries 3 --customRoutes ${customRouteFilePath}" + + def processes = [:] + + for (int i = 0; i < platforms.size(); i++) { + def platform = platforms.get(i) + def buckets = platform.buckets ?: 1 + + for (int bucket = 1; bucket <= buckets; bucket++) { + // clousure var - don't inline or jenkins complains + def currBucket = bucket + def suffix = currBucket == 1 ? '' : "-${currBucket}" + + // Run using remote provider + if (platform.provider) { + def name = "${platform.browser}-${platform.provider}${suffix}" + processes[name] = { + stage("${name}") { + bedrockRemoteTools.tinyWorkSishTunnel() + bedrockRemoteTools.withRemoteCreds(platform.provider) { + String customArgs = additionalArgs + " --remote ${platform.provider}" + if (platform.provider == "aws") { + customArgs = customArgs + " --sishDomain \"sish.osu.tiny.work\"" + } + if (platform.os) { + customArgs = customArgs + " --platformName \"${platform.os}\"" + } + + generateNPMVersionRouteFile(npm_tag: args.npm_tag, filePath: customRouteFilePath) + updateDependenciesWithTag(npm_tag: args.npm_tag) + bedrockTests( + name: name, + browser: platform.browser, + testDirs: [ "tinymce-angular-component/src/test/ts" ], + bucket: currBucket, + buckets: buckets, + custom: customArgs + ) + } + } + } + } else { + // Headless code in case is needed + def name = "headless-${platform.browser}${suffix}" + processes[name] = { + stage("${name}") { + generateNPMVersionRouteFile(npm_tag: args.npm_tag, filePath: customRouteFilePath) + updateDependenciesWithTag(npm_tag: args.npm_tag) + bedrockTests( + name: name, + browser: platform.browser, + testDirs: [ "tinymce-angular-component/src/test/ts" ], + bucket: currBucket, + buckets: buckets, + custom: additionalArgs + " --useSelenium" + ) + } + } + } + } + } + + parallel processes +} + +timestamps { + tinyPods.node() { + stage('deps') { + yarnInstall() + } + + stage('build') { + sh 'yarn build' + } + + stage('tests') { + runSmokeTests(npm_tag: params.NPM_TAG ?: 'latest') + } + } +} diff --git a/package.json b/package.json index 230ae438..9f7ad2ee 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "license": "MIT", "scripts": { "preinstall": "node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('You must use Yarn to install, not NPM')\"", - "test": "yarn bedrock-auto -b chrome-headless -f tinymce-angular-component/src/test/ts/**/*Test.ts", + "test": "yarn bedrock-auto -b chrome-headless -f tinymce-angular-component/src/test/ts/browser/*Test.ts", "test-manual": "bedrock -f tinymce-angular-component/src/test/ts/**/*Test.ts", "clean": "yarn rimraf dist", "lint": "eslint tinymce-angular-component/src/**/*.ts stories/**/*.ts", diff --git a/tinymce-angular-component/src/test/ts/alien/TestHelpers.ts b/tinymce-angular-component/src/test/ts/alien/TestHelpers.ts index 8d7db947..f41e091c 100644 --- a/tinymce-angular-component/src/test/ts/alien/TestHelpers.ts +++ b/tinymce-angular-component/src/test/ts/alien/TestHelpers.ts @@ -4,12 +4,14 @@ import { ScriptLoader } from '../../../main/ts/utils/ScriptLoader'; import { Attribute, Remove, SelectorFilter, SugarElement } from '@ephox/sugar'; import { ComponentFixture } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { EditorComponent } from '../../../main/ts/editor/editor.component'; +import { EditorComponent, Version } from '../../../main/ts/editor/editor.component'; import type { Editor } from 'tinymce'; import { Keyboard, Keys } from '@ephox/agar'; export const apiKey = Fun.constant('qagffr3pkuv17a8on1afax661irst1hbr4e6tbv888sz91jc'); +export const supportedTinymceVersions = Fun.constant([ '5', '6', '7', '8' ]); + export const throwTimeout = (timeoutMs: number, message: string = `Timeout ${timeoutMs}ms`) => (source: Observable) => diff --git a/tinymce-angular-component/src/test/ts/alien/TestHooks.ts b/tinymce-angular-component/src/test/ts/alien/TestHooks.ts index 121dcad6..262e5e57 100644 --- a/tinymce-angular-component/src/test/ts/alien/TestHooks.ts +++ b/tinymce-angular-component/src/test/ts/alien/TestHooks.ts @@ -1,6 +1,6 @@ import { after, before, beforeEach, context } from '@ephox/bedrock-client'; import { ComponentFixture, TestBed, TestModuleMetadata } from '@angular/core/testing'; -import { Type } from '@angular/core'; +import { Type, VERSION } from '@angular/core'; import { EditorComponent, Version } from '../../../main/ts/editor/editor.component'; import { firstValueFrom, map, switchMap, tap } from 'rxjs'; import { By } from '@angular/platform-browser'; @@ -9,6 +9,7 @@ import { VersionLoader } from '@tinymce/miniature'; import { deleteTinymce, throwTimeout } from './TestHelpers'; import { FormsModule, ReactiveFormsModule, NgModel } from '@angular/forms'; import type { Editor } from 'tinymce'; +import { Attribute, SugarElement } from '@ephox/sugar'; export const fixtureHook = (component: Type, moduleDef: TestModuleMetadata) => { before(async () => { @@ -69,15 +70,19 @@ export const editorHook = (component: Type, moduleDef: TestModul fixture.detectChanges(); return firstValueFrom( - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + editorComponent.onInit.pipe( throwTimeout(10000, `Timed out waiting for editor to load`), switchMap( ({ editor }) => new Promise((resolve) => { editor.once('SkinLoaded', () => { + // Bake the Angular version as a data attribute on the editor container so it can be verified in the test + const container = editor.getContainer(); + Attribute.set(SugarElement.fromDom( container), 'data-framework-version', VERSION.full); + // This is a workaround to avoid a race condition occurring in tinymce 8 where licenseKeyManager is still validating the license key - // after global tinymce is removed in a clean up. Specifically, it happens when unloading/loading different versions of TinyMCE + // after global tinymce is removed in a clean up. Specifically, it happens when unloading/loading different versions of TinyMCE if (editor.licenseKeyManager) { editor.licenseKeyManager.validate({}).then(() => { resolve(editor as Editor); diff --git a/tinymce-angular-component/src/test/ts/browser/LoadTinyTest.ts b/tinymce-angular-component/src/test/ts/browser/LoadTinyTest.ts index c6780ffc..9b00970e 100644 --- a/tinymce-angular-component/src/test/ts/browser/LoadTinyTest.ts +++ b/tinymce-angular-component/src/test/ts/browser/LoadTinyTest.ts @@ -8,7 +8,7 @@ import { EditorComponent, TINYMCE_SCRIPT_SRC } from '../../../main/ts/public_api import { Version } from '../../../main/ts/editor/editor.component'; import { editorHook, tinymceVersionHook } from '../alien/TestHooks'; import type { Editor } from 'tinymce'; -import { apiKey, deleteTinymce } from '../alien/TestHelpers'; +import { apiKey, deleteTinymce, supportedTinymceVersions } from '../alien/TestHelpers'; describe('LoadTinyTest', () => { const key = apiKey(); @@ -17,7 +17,7 @@ describe('LoadTinyTest', () => { Assertions.assertEq(`Loaded version of TinyMCE should be ${version}`, version, Global.tinymce.majorVersion); }; - for (const version of [ '4', '5', '6', '7', '8' ] as Version[]) { + for (const version of supportedTinymceVersions()) { context(`With local version ${version}`, () => { const createFixture = editorHook(EditorComponent, { providers: [ @@ -47,7 +47,7 @@ describe('LoadTinyTest', () => { }); } - for (const version of [ '5', '6', '7', '8' ] as Version[]) { + for (const version of supportedTinymceVersions()) { context(`With cloud version ${version}`, () => { const createFixture = editorHook(EditorComponent); diff --git a/tinymce-angular-component/src/test/ts/smoke-test/VerifyIntegrationTest.ts b/tinymce-angular-component/src/test/ts/smoke-test/VerifyIntegrationTest.ts new file mode 100644 index 00000000..235222b2 --- /dev/null +++ b/tinymce-angular-component/src/test/ts/smoke-test/VerifyIntegrationTest.ts @@ -0,0 +1,54 @@ +import '../alien/InitTestEnvironment'; + +import { Assertions } from '@ephox/agar'; +import { describe, it, context, before } from '@ephox/bedrock-client'; +import { Global } from '@ephox/katamari'; +import { SugarElement, Attribute } from '@ephox/sugar'; + +import { EditorComponent, TINYMCE_SCRIPT_SRC } from '../../../main/ts/public_api'; +import { Version } from '../../../main/ts/editor/editor.component'; +import { editorHook } from '../alien/TestHooks'; +import type { Editor } from 'tinymce'; +import { deleteTinymce, supportedTinymceVersions } from '../alien/TestHelpers'; + +/* + This test requires the targeted Angular version provided via custom route +*/ +describe('VerifyIntegrationTest', () => { + interface IntegrationInfo { + version: string; + } + + const assertTinymceVersion = (version: Version, editor: Editor) => { + Assertions.assertEq(`Loaded version of TinyMCE should be ${version}`, version, editor.editorManager.majorVersion); + Assertions.assertEq(`Loaded version of TinyMCE should be ${version}`, version, Global.tinymce.majorVersion); + }; + + for (const version of supportedTinymceVersions()) { + context(`With local Tinymce version ${version}`, () => { + const createFixture = editorHook(EditorComponent, { + providers: [ + { + provide: TINYMCE_SCRIPT_SRC, + useValue: `/project/node_modules/tinymce-${version}/tinymce.min.js`, + }, + ], + }); + + before(deleteTinymce); + + it('Should be able to load with the specified Angular version', async () => { + const { editor } = await createFixture(); + const integrationInfo = await window.fetch('/custom/integration/info').then((resp) => resp.json()) as IntegrationInfo; + const container = editor.getContainer(); + + Assertions.assertEq(`Angular version should be ${integrationInfo.version}`, + true, + Attribute.get(SugarElement.fromDom(container), 'data-framework-version') === integrationInfo.version + ); + + assertTinymceVersion(version, editor); + }); + }); + } +});