Best JavaScript code snippet using cypress
project_spec.js
Source:project_spec.js
1require('../spec_helper')2const mockedEnv = require('mocked-env')3const path = require('path')4const commitInfo = require('@cypress/commit-info')5const Fixtures = require('../support/helpers/fixtures')6const api = require(`${root}lib/api`)7const user = require(`${root}lib/user`)8const cache = require(`${root}lib/cache`)9const config = require(`${root}lib/config`)10const scaffold = require(`${root}lib/scaffold`)11const { ServerE2E } = require(`${root}lib/server-e2e`)12const { ProjectE2E } = require(`${root}lib/project-e2e`)13const Automation = require(`${root}lib/automation`)14const savedState = require(`${root}lib/saved_state`)15const preprocessor = require(`${root}lib/plugins/preprocessor`)16const plugins = require(`${root}lib/plugins`)17const { fs } = require(`${root}lib/util/fs`)18const settings = require(`${root}lib/util/settings`)19const Watchers = require(`${root}lib/watchers`)20const { SocketE2E } = require(`${root}lib/socket-e2e`)21xdescribe('lib/project-e2e', () => {22 beforeEach(function () {23 Fixtures.scaffold()24 this.todosPath = Fixtures.projectPath('todos')25 this.idsPath = Fixtures.projectPath('ids')26 this.pristinePath = Fixtures.projectPath('pristine')27 return settings.read(this.todosPath).then((obj = {}) => {28 ({ projectId: this.projectId } = obj)29 return config.set({ projectName: 'project', projectRoot: '/foo/bar' })30 .then((config1) => {31 this.config = config132 this.project = new ProjectE2E(this.todosPath)33 })34 })35 })36 afterEach(function () {37 Fixtures.remove()38 if (this.project) {39 this.project.close()40 }41 })42 it('requires a projectRoot', () => {43 const fn = () => new ProjectE2E()44 expect(fn).to.throw('Instantiating lib/project requires a projectRoot!')45 })46 it('always resolves the projectRoot to be absolute', () => {47 const p = new ProjectE2E('../foo/bar')48 expect(p.projectRoot).not.to.eq('../foo/bar')49 expect(p.projectRoot).to.eq(path.resolve('../foo/bar'))50 })51 context('#saveState', () => {52 beforeEach(function () {53 const integrationFolder = 'the/save/state/test'54 sinon.stub(config, 'get').withArgs(this.todosPath).resolves({ integrationFolder })55 sinon.stub(this.project, 'determineIsNewProject').withArgs(integrationFolder).resolves(false)56 this.project.cfg = { integrationFolder }57 return savedState.create(this.project.projectRoot)58 .then((state) => state.remove())59 })60 afterEach(function () {61 return savedState.create(this.project.projectRoot)62 .then((state) => state.remove())63 })64 it('saves state without modification', function () {65 return this.project.saveState()66 .then((state) => expect(state).to.deep.eq({}))67 })68 it('adds property', function () {69 return this.project.saveState()70 .then(() => this.project.saveState({ foo: 42 }))71 .then((state) => expect(state).to.deep.eq({ foo: 42 }))72 })73 it('adds second property', function () {74 return this.project.saveState()75 .then(() => this.project.saveState({ foo: 42 }))76 .then(() => this.project.saveState({ bar: true }))77 .then((state) => expect(state).to.deep.eq({ foo: 42, bar: true }))78 })79 it('modifes property', function () {80 return this.project.saveState()81 .then(() => this.project.saveState({ foo: 42 }))82 .then(() => this.project.saveState({ foo: 'modified' }))83 .then((state) => expect(state).to.deep.eq({ foo: 'modified' }))84 })85 })86 context('#getConfig', () => {87 const integrationFolder = 'foo/bar/baz'88 beforeEach(function () {89 sinon.stub(config, 'get').withArgs(this.todosPath, { foo: 'bar' }).resolves({ baz: 'quux', integrationFolder })90 sinon.stub(this.project, 'determineIsNewProject').withArgs(integrationFolder).resolves(false)91 })92 it('calls config.get with projectRoot + options + saved state', function () {93 return savedState.create(this.todosPath)94 .then((state) => {95 sinon.stub(state, 'get').resolves({ reporterWidth: 225 })96 this.project.getConfig({ foo: 'bar' })97 .then((cfg) => {98 expect(cfg).to.deep.eq({99 integrationFolder,100 isNewProject: false,101 baz: 'quux',102 state: {103 reporterWidth: 225,104 },105 })106 })107 })108 })109 it('resolves if cfg is already set', function () {110 this.project.cfg = {111 integrationFolder,112 foo: 'bar',113 }114 return this.project.getConfig()115 .then((cfg) => {116 expect(cfg).to.deep.eq({117 integrationFolder,118 foo: 'bar',119 })120 })121 })122 it('sets cfg.isNewProject to false when state.showedOnBoardingModal is true', function () {123 return savedState.create(this.todosPath)124 .then((state) => {125 sinon.stub(state, 'get').resolves({ showedOnBoardingModal: true })126 this.project.getConfig({ foo: 'bar' })127 .then((cfg) => {128 expect(cfg).to.deep.eq({129 integrationFolder,130 isNewProject: false,131 baz: 'quux',132 state: {133 showedOnBoardingModal: true,134 },135 })136 })137 })138 })139 it('does not set cfg.isNewProject when cfg.isTextTerminal', function () {140 const cfg = { isTextTerminal: true }141 config.get.resolves(cfg)142 sinon.stub(this.project, '_setSavedState').resolves(cfg)143 return this.project.getConfig({ foo: 'bar' })144 .then((cfg) => {145 expect(cfg).not.to.have.property('isNewProject')146 })147 })148 })149 context('#open', () => {150 beforeEach(function () {151 sinon.stub(this.project, 'watchSettingsAndStartWebsockets').resolves()152 sinon.stub(this.project, 'checkSupportFile').resolves()153 sinon.stub(this.project, 'scaffold').resolves()154 sinon.stub(this.project, 'getConfig').resolves(this.config)155 sinon.stub(ServerE2E.prototype, 'open').resolves([])156 sinon.stub(ServerE2E.prototype, 'reset')157 sinon.stub(config, 'updateWithPluginValues').returns(this.config)158 sinon.stub(scaffold, 'plugins').resolves()159 sinon.stub(plugins, 'init').resolves()160 })161 it('calls #watchSettingsAndStartWebsockets with options + config', function () {162 const opts = { changeEvents: false, onAutomationRequest () {} }163 this.project.cfg = {}164 return this.project.open(opts).then(() => {165 expect(this.project.watchSettingsAndStartWebsockets).to.be.calledWith(opts, this.project.cfg)166 })167 })168 it('calls #scaffold with server config promise', function () {169 return this.project.open().then(() => {170 expect(this.project.scaffold).to.be.calledWith(this.config)171 })172 })173 it('calls #checkSupportFile with server config when scaffolding is finished', function () {174 return this.project.open().then(() => {175 expect(this.project.checkSupportFile).to.be.calledWith(this.config)176 })177 })178 it('calls #getConfig options', function () {179 const opts = {}180 return this.project.open(opts).then(() => {181 expect(this.project.getConfig).to.be.calledWith(opts)182 })183 })184 it('initializes the plugins', function () {185 return this.project.open({}).then(() => {186 expect(plugins.init).to.be.called187 })188 })189 it('calls support.plugins with pluginsFile directory', function () {190 return this.project.open({}).then(() => {191 expect(scaffold.plugins).to.be.calledWith(path.dirname(this.config.pluginsFile))192 })193 })194 it('calls options.onError with plugins error when there is a plugins error', function () {195 const onError = sinon.spy()196 const err = {197 name: 'plugin error name',198 message: 'plugin error message',199 }200 return this.project.open({ onError }).then(() => {201 const pluginsOnError = plugins.init.lastCall.args[1].onError202 expect(pluginsOnError).to.be.a('function')203 pluginsOnError(err)204 expect(onError).to.be.calledWith(err)205 })206 })207 it('updates config.state when saved state changes', function () {208 sinon.spy(this.project, 'saveState')209 const options = {}210 return this.project.open(options)211 .then(() => options.onSavedStateChanged({ autoScrollingEnabled: false }))212 .then(() => this.project.getConfig())213 .then((config) => {214 expect(this.project.saveState).to.be.calledWith({ autoScrollingEnabled: false })215 expect(config.state).to.eql({ autoScrollingEnabled: false })216 })217 })218 // TODO: skip this for now219 it.skip('watches cypress.json', function () {220 return this.server.open().bind(this).then(() => {221 expect(Watchers.prototype.watch).to.be.calledWith('/Users/brian/app/cypress.json')222 })223 })224 // TODO: skip this for now225 it.skip('passes watchers to Socket.startListening', function () {226 const options = {}227 return this.server.open(options).then(() => {228 const { startListening } = SocketE2E.prototype229 expect(startListening.getCall(0).args[0]).to.be.instanceof(Watchers)230 expect(startListening.getCall(0).args[1]).to.eq(options)231 })232 })233 it('attaches warning to non-chrome browsers when chromeWebSecurity:false', function () {234 Object.assign(this.config, {235 browsers: [{ family: 'chromium', name: 'Canary' }, { family: 'some-other-family', name: 'some-other-name' }],236 chromeWebSecurity: false,237 })238 return this.project.open()239 .then(() => this.project.getConfig())240 .then((config) => {241 expect(config.chromeWebSecurity).eq(false)242 expect(config.browsers).deep.eq([243 {244 family: 'chromium',245 name: 'Canary',246 },247 {248 family: 'some-other-family',249 name: 'some-other-name',250 warning: `\251Your project has set the configuration option: \`chromeWebSecurity: false\`252This option will not have an effect in Some-other-name. Tests that rely on web security being disabled will not run as expected.\253`,254 },255 ])256 expect(config).ok257 })258 })259 })260 context('#close', () => {261 beforeEach(function () {262 this.project = new ProjectE2E('/_test-output/path/to/project-e2e')263 sinon.stub(this.project, 'getConfig').resolves(this.config)264 sinon.stub(user, 'ensureAuthToken').resolves('auth-token-123')265 })266 it('closes server', function () {267 this.project.server = sinon.stub({ close () {} })268 return this.project.close().then(() => {269 expect(this.project.server.close).to.be.calledOnce270 })271 })272 it('closes watchers', function () {273 this.project.watchers = sinon.stub({ close () {} })274 return this.project.close().then(() => {275 expect(this.project.watchers.close).to.be.calledOnce276 })277 })278 it('can close when server + watchers arent open', function () {279 return this.project.close()280 })281 })282 context('#reset', () => {283 beforeEach(function () {284 this.project = new ProjectE2E(this.pristinePath)285 this.project.automation = { reset: sinon.stub() }286 this.project.server = { reset: sinon.stub() }287 })288 it('resets server + automation', function () {289 return this.project.reset()290 .then(() => {291 expect(this.project.automation.reset).to.be.calledOnce292 expect(this.project.server.reset).to.be.calledOnce293 })294 })295 })296 context('#getRuns', () => {297 beforeEach(function () {298 this.project = new ProjectE2E(this.todosPath)299 sinon.stub(settings, 'read').resolves({ projectId: 'id-123' })300 sinon.stub(api, 'getProjectRuns').resolves('runs')301 sinon.stub(user, 'ensureAuthToken').resolves('auth-token-123')302 })303 it('calls api.getProjectRuns with project id + session', function () {304 return this.project.getRuns().then((runs) => {305 expect(api.getProjectRuns).to.be.calledWith('id-123', 'auth-token-123')306 expect(runs).to.equal('runs')307 })308 })309 })310 context('#scaffold', () => {311 beforeEach(function () {312 this.project = new ProjectE2E('/_test-output/path/to/project-e2e')313 sinon.stub(scaffold, 'integration').resolves()314 sinon.stub(scaffold, 'fixture').resolves()315 sinon.stub(scaffold, 'support').resolves()316 sinon.stub(scaffold, 'plugins').resolves()317 this.obj = { projectRoot: 'pr', fixturesFolder: 'ff', integrationFolder: 'if', supportFolder: 'sf', pluginsFile: 'pf/index.js' }318 })319 it('calls scaffold.integration with integrationFolder', function () {320 return this.project.scaffold(this.obj).then(() => {321 expect(scaffold.integration).to.be.calledWith(this.obj.integrationFolder)322 })323 })324 it('calls fixture.scaffold with fixturesFolder', function () {325 return this.project.scaffold(this.obj).then(() => {326 expect(scaffold.fixture).to.be.calledWith(this.obj.fixturesFolder)327 })328 })329 it('calls support.scaffold with supportFolder', function () {330 return this.project.scaffold(this.obj).then(() => {331 expect(scaffold.support).to.be.calledWith(this.obj.supportFolder)332 })333 })334 it('does not call support.plugins if config.pluginsFile is falsey', function () {335 this.obj.pluginsFile = false336 return this.project.scaffold(this.obj).then(() => {337 expect(scaffold.plugins).not.to.be.called338 })339 })340 describe('forced', () => {341 let resetEnv342 beforeEach(function () {343 this.obj.isTextTerminal = true344 resetEnv = mockedEnv({345 CYPRESS_INTERNAL_FORCE_SCAFFOLD: '1',346 })347 })348 afterEach(() => {349 resetEnv()350 })351 it('calls scaffold when forced by environment variable', function () {352 return this.project.scaffold(this.obj).then(() => {353 expect(scaffold.integration).to.be.calledWith(this.obj.integrationFolder)354 expect(scaffold.fixture).to.be.calledWith(this.obj.fixturesFolder)355 expect(scaffold.support).to.be.calledWith(this.obj.supportFolder)356 })357 })358 })359 describe('not forced', () => {360 let resetEnv361 beforeEach(function () {362 this.obj.isTextTerminal = true363 resetEnv = mockedEnv({364 CYPRESS_INTERNAL_FORCE_SCAFFOLD: undefined,365 })366 })367 afterEach(() => {368 resetEnv()369 })370 it('does not scaffold integration folder', function () {371 return this.project.scaffold(this.obj).then(() => {372 expect(scaffold.integration).to.not.be.calledWith(this.obj.integrationFolder)373 expect(scaffold.fixture).to.not.be.calledWith(this.obj.fixturesFolder)374 // still scaffolds support folder due to old logic375 expect(scaffold.support).to.be.calledWith(this.obj.supportFolder)376 })377 })378 })379 })380 context('#watchSettings', () => {381 beforeEach(function () {382 this.project = new ProjectE2E('/_test-output/path/to/project-e2e')383 this.project.server = { startWebsockets () {} }384 sinon.stub(settings, 'pathToConfigFile').returns('/path/to/cypress.json')385 sinon.stub(settings, 'pathToCypressEnvJson').returns('/path/to/cypress.env.json')386 this.watch = sinon.stub(this.project.watchers, 'watch')387 })388 it('watches cypress.json and cypress.env.json', function () {389 this.project.watchSettingsAndStartWebsockets({ onSettingsChanged () {} })390 expect(this.watch).to.be.calledTwice391 expect(this.watch).to.be.calledWith('/path/to/cypress.json')392 expect(this.watch).to.be.calledWith('/path/to/cypress.env.json')393 })394 it('sets onChange event when {changeEvents: true}', function (done) {395 this.project.watchSettingsAndStartWebsockets({ onSettingsChanged: () => done() })396 // get the object passed to watchers.watch397 const obj = this.watch.getCall(0).args[1]398 expect(obj.onChange).to.be.a('function')399 obj.onChange()400 })401 it('does not call watch when {changeEvents: false}', function () {402 this.project.watchSettingsAndStartWebsockets({ onSettingsChanged: undefined })403 expect(this.watch).not.to.be.called404 })405 it('does not call onSettingsChanged when generatedProjectIdTimestamp is less than 1 second', function () {406 let timestamp = new Date()407 this.project.generatedProjectIdTimestamp = timestamp408 const stub = sinon.stub()409 this.project.watchSettingsAndStartWebsockets({ onSettingsChanged: stub })410 // get the object passed to watchers.watch411 const obj = this.watch.getCall(0).args[1]412 obj.onChange()413 expect(stub).not.to.be.called414 // subtract 1 second from our timestamp415 timestamp.setSeconds(timestamp.getSeconds() - 1)416 obj.onChange()417 expect(stub).to.be.calledOnce418 })419 })420 context('#checkSupportFile', () => {421 beforeEach(function () {422 sinon.stub(fs, 'pathExists').resolves(true)423 this.project = new ProjectE2E('/_test-output/path/to/project-e2e')424 this.project.server = { onTestFileChange: sinon.spy() }425 sinon.stub(preprocessor, 'getFile').resolves()426 this.config = {427 projectRoot: '/path/to/root/',428 supportFile: '/path/to/root/foo/bar.js',429 }430 })431 it('does nothing when {supportFile: false}', function () {432 const ret = this.project.checkSupportFile({ supportFile: false })433 expect(ret).to.be.undefined434 })435 it('throws when support file does not exist', function () {436 fs.pathExists.resolves(false)437 return this.project.checkSupportFile(this.config)438 .catch((e) => {439 expect(e.message).to.include('The support file is missing or invalid.')440 })441 })442 })443 context('#watchPluginsFile', () => {444 beforeEach(function () {445 sinon.stub(fs, 'pathExists').resolves(true)446 this.project = new ProjectE2E('/_test-output/path/to/project-e2e')447 this.project.watchers = { watchTree: sinon.spy() }448 sinon.stub(plugins, 'init').resolves()449 this.config = {450 pluginsFile: '/path/to/plugins-file',451 }452 })453 it('does nothing when {pluginsFile: false}', function () {454 this.config.pluginsFile = false455 return this.project.watchPluginsFile(this.config, {}).then(() => {456 expect(this.project.watchers.watchTree).not.to.be.called457 })458 })459 it('does nothing if pluginsFile does not exist', function () {460 fs.pathExists.resolves(false)461 return this.project.watchPluginsFile(this.config, {}).then(() => {462 expect(this.project.watchers.watchTree).not.to.be.called463 })464 })465 it('does nothing if in run mode', function () {466 return this.project.watchPluginsFile(this.config, {467 isTextTerminal: true,468 }).then(() => {469 expect(this.project.watchers.watchTree).not.to.be.called470 })471 })472 it('watches the pluginsFile', function () {473 return this.project.watchPluginsFile(this.config, {}).then(() => {474 expect(this.project.watchers.watchTree).to.be.calledWith(this.config.pluginsFile)475 expect(this.project.watchers.watchTree.lastCall.args[1]).to.be.an('object')476 expect(this.project.watchers.watchTree.lastCall.args[1].onChange).to.be.a('function')477 })478 })479 it('calls plugins.init when file changes', function () {480 return this.project.watchPluginsFile(this.config, {}).then(() => {481 this.project.watchers.watchTree.firstCall.args[1].onChange()482 expect(plugins.init).to.be.calledWith(this.config)483 })484 })485 it('handles errors from calling plugins.init', function (done) {486 const error = { name: 'foo', message: 'foo' }487 plugins.init.rejects(error)488 this.project.watchPluginsFile(this.config, {489 onError (err) {490 expect(err).to.eql(error)491 done()492 },493 })494 .then(() => {495 this.project.watchers.watchTree.firstCall.args[1].onChange()496 })497 })498 })499 context('#watchSettingsAndStartWebsockets', () => {500 beforeEach(function () {501 this.project = new ProjectE2E('/_test-output/path/to/project-e2e')502 this.project.watchers = {}503 this.project.server = sinon.stub({ startWebsockets () {} })504 sinon.stub(this.project, 'watchSettings')505 sinon.stub(Automation, 'create').returns('automation')506 })507 it('calls server.startWebsockets with automation + config', function () {508 const c = {}509 this.project.watchSettingsAndStartWebsockets({}, c)510 expect(this.project.server.startWebsockets).to.be.calledWith('automation', c)511 })512 it('passes onReloadBrowser callback', function () {513 const fn = sinon.stub()514 this.project.server.startWebsockets.yieldsTo('onReloadBrowser')515 this.project.watchSettingsAndStartWebsockets({ onReloadBrowser: fn }, {})516 expect(fn).to.be.calledOnce517 })518 })519 context('#getProjectId', () => {520 beforeEach(function () {521 this.project = new ProjectE2E('/_test-output/path/to/project-e2e')522 this.verifyExistence = sinon.stub(ProjectE2E.prototype, 'verifyExistence').resolves()523 })524 it('calls verifyExistence', function () {525 sinon.stub(settings, 'read').resolves({ projectId: 'id-123' })526 return this.project.getProjectId()527 .then(() => expect(this.verifyExistence).to.be.calledOnce)528 })529 it('returns the project id from settings', function () {530 sinon.stub(settings, 'read').resolves({ projectId: 'id-123' })531 return this.project.getProjectId()532 .then((id) => expect(id).to.eq('id-123'))533 })534 it('throws NO_PROJECT_ID with the projectRoot when no projectId was found', function () {535 sinon.stub(settings, 'read').resolves({})536 return this.project.getProjectId()537 .then((id) => {538 throw new Error('expected to fail, but did not')539 }).catch((err) => {540 expect(err.type).to.eq('NO_PROJECT_ID')541 expect(err.message).to.include('/_test-output/path/to/project-e2e')542 })543 })544 it('bubbles up Settings.read errors', function () {545 const err = new Error()546 err.code = 'EACCES'547 sinon.stub(settings, 'read').rejects(err)548 return this.project.getProjectId()549 .then((id) => {550 throw new Error('expected to fail, but did not')551 }).catch((err) => {552 expect(err.code).to.eq('EACCES')553 })554 })555 })556 context('#writeProjectId', () => {557 beforeEach(function () {558 this.project = new ProjectE2E('/_test-output/path/to/project-e2e')559 sinon.stub(settings, 'write')560 .withArgs(this.project.projectRoot, { projectId: 'id-123' })561 .resolves({ projectId: 'id-123' })562 })563 it('calls Settings.write with projectRoot and attrs', function () {564 return this.project.writeProjectId('id-123').then((id) => {565 expect(id).to.eq('id-123')566 })567 })568 it('sets generatedProjectIdTimestamp', function () {569 return this.project.writeProjectId('id-123').then(() => {570 expect(this.project.generatedProjectIdTimestamp).to.be.a('date')571 })572 })573 })574 context('#getSpecUrl', () => {575 beforeEach(function () {576 this.project2 = new ProjectE2E(this.idsPath)577 return settings.write(this.idsPath, { port: 2020 })578 })579 it('returns fully qualified url when spec exists', function () {580 return this.project2.getSpecUrl('cypress/integration/bar.js')581 .then((str) => {582 expect(str).to.eq('http://localhost:2020/__/#/tests/integration/bar.js')583 })584 })585 it('returns fully qualified url on absolute path to spec', function () {586 const todosSpec = path.join(this.todosPath, 'tests/sub/sub_test.coffee')587 return this.project.getSpecUrl(todosSpec)588 .then((str) => {589 expect(str).to.eq('http://localhost:8888/__/#/tests/integration/sub/sub_test.coffee')590 })591 })592 it('escapses %, &', function () {593 const todosSpec = path.join(this.todosPath, 'tests/sub/a&b%c.js')594 return this.project.getSpecUrl(todosSpec)595 .then((str) => {596 expect(str).to.eq('http://localhost:8888/__/#/tests/integration/sub/a%26b%25c.js')597 })598 })599 // ? is invalid in Windows, but it can be tested here600 // because it's a unit test and doesn't check the existence of files601 it('escapes ?', function () {602 const todosSpec = path.join(this.todosPath, 'tests/sub/a?.spec.js')603 return this.project.getSpecUrl(todosSpec)604 .then((str) => {605 expect(str).to.eq('http://localhost:8888/__/#/tests/integration/sub/a%3F.spec.js')606 })607 })608 it('escapes %, &, ? in the url dir', function () {609 const todosSpec = path.join(this.todosPath, 'tests/s%&?ub/a.spec.js')610 return this.project.getSpecUrl(todosSpec)611 .then((str) => {612 expect(str).to.eq('http://localhost:8888/__/#/tests/integration/s%25%26%3Fub/a.spec.js')613 })614 })615 it('returns __all spec url', function () {616 return this.project.getSpecUrl()617 .then((str) => {618 expect(str).to.eq('http://localhost:8888/__/#/tests/__all')619 })620 })621 it('returns __all spec url with spec is __all', function () {622 return this.project.getSpecUrl('__all')623 .then((str) => {624 expect(str).to.eq('http://localhost:8888/__/#/tests/__all')625 })626 })627 })628 context('.add', () => {629 beforeEach(function () {630 this.pristinePath = Fixtures.projectPath('pristine')631 })632 it('inserts path into cache', function () {633 return ProjectE2E.add(this.pristinePath, {})634 .then(() => cache.read()).then((json) => {635 expect(json.PROJECTS).to.deep.eq([this.pristinePath])636 })637 })638 describe('if project at path has id', () => {639 it('returns object containing path and id', function () {640 sinon.stub(settings, 'read').resolves({ projectId: 'id-123' })641 return ProjectE2E.add(this.pristinePath, {})642 .then((project) => {643 expect(project.id).to.equal('id-123')644 expect(project.path).to.equal(this.pristinePath)645 })646 })647 })648 describe('if project at path does not have id', () => {649 it('returns object containing just the path', function () {650 sinon.stub(settings, 'read').rejects()651 return ProjectE2E.add(this.pristinePath, {})652 .then((project) => {653 expect(project.id).to.be.undefined654 expect(project.path).to.equal(this.pristinePath)655 })656 })657 })658 describe('if configFile is non-default', () => {659 it('doesn\'t cache anything and returns object containing just the path', function () {660 return ProjectE2E.add(this.pristinePath, { configFile: false })661 .then((project) => {662 expect(project.id).to.be.undefined663 expect(project.path).to.equal(this.pristinePath)664 return cache.read()665 }).then((json) => {666 expect(json.PROJECTS).to.deep.eq([])667 })668 })669 })670 })671 context('#createCiProject', () => {672 beforeEach(function () {673 this.project = new ProjectE2E('/_test-output/path/to/project-e2e')674 this.newProject = { id: 'project-id-123' }675 sinon.stub(this.project, 'writeProjectId').resolves('project-id-123')676 sinon.stub(user, 'ensureAuthToken').resolves('auth-token-123')677 sinon.stub(commitInfo, 'getRemoteOrigin').resolves('remoteOrigin')678 sinon.stub(api, 'createProject')679 .withArgs({ foo: 'bar' }, 'remoteOrigin', 'auth-token-123')680 .resolves(this.newProject)681 })682 it('calls api.createProject with user session', function () {683 return this.project.createCiProject({ foo: 'bar' }).then(() => {684 expect(api.createProject).to.be.calledWith({ foo: 'bar' }, 'remoteOrigin', 'auth-token-123')685 })686 })687 it('calls writeProjectId with id', function () {688 return this.project.createCiProject({ foo: 'bar' }).then(() => {689 expect(this.project.writeProjectId).to.be.calledWith('project-id-123')690 })691 })692 it('returns project id', function () {693 return this.project.createCiProject({ foo: 'bar' }).then((projectId) => {694 expect(projectId).to.eql(this.newProject)695 })696 })697 })698 context('#getRecordKeys', () => {699 beforeEach(function () {700 this.recordKeys = []701 this.project = new ProjectE2E(this.pristinePath)702 sinon.stub(settings, 'read').resolves({ projectId: 'id-123' })703 sinon.stub(user, 'ensureAuthToken').resolves('auth-token-123')704 sinon.stub(api, 'getProjectRecordKeys').resolves(this.recordKeys)705 })706 it('calls api.getProjectRecordKeys with project id + session', function () {707 return this.project.getRecordKeys().then(() => {708 expect(api.getProjectRecordKeys).to.be.calledWith('id-123', 'auth-token-123')709 })710 })711 it('returns ci keys', function () {712 return this.project.getRecordKeys().then((recordKeys) => {713 expect(recordKeys).to.equal(this.recordKeys)714 })715 })716 })717 context('#requestAccess', () => {718 beforeEach(function () {719 this.project = new ProjectE2E(this.pristinePath)720 sinon.stub(user, 'ensureAuthToken').resolves('auth-token-123')721 sinon.stub(api, 'requestAccess').resolves('response')722 })723 it('calls api.requestAccess with project id + auth token', function () {724 return this.project.requestAccess('project-id-123').then(() => {725 expect(api.requestAccess).to.be.calledWith('project-id-123', 'auth-token-123')726 })727 })728 it('returns response', function () {729 return this.project.requestAccess('project-id-123').then((response) => {730 expect(response).to.equal('response')731 })732 })733 })734 context('.remove', () => {735 beforeEach(() => {736 sinon.stub(cache, 'removeProject').resolves()737 })738 it('calls cache.removeProject with path', () => {739 return ProjectE2E.remove('/_test-output/path/to/project-e2e').then(() => {740 expect(cache.removeProject).to.be.calledWith('/_test-output/path/to/project-e2e')741 })742 })743 })744 context('.id', () => {745 it('returns project id', function () {746 return ProjectE2E.id(this.todosPath).then((id) => {747 expect(id).to.eq(this.projectId)748 })749 })750 })751 context('.getOrgs', () => {752 beforeEach(() => {753 sinon.stub(user, 'ensureAuthToken').resolves('auth-token-123')754 sinon.stub(api, 'getOrgs').resolves([])755 })756 it('calls api.getOrgs', () => {757 return ProjectE2E.getOrgs().then((orgs) => {758 expect(orgs).to.deep.eq([])759 expect(api.getOrgs).to.be.calledOnce760 expect(api.getOrgs).to.be.calledWith('auth-token-123')761 })762 })763 })764 context('.paths', () => {765 beforeEach(() => {766 sinon.stub(cache, 'getProjectRoots').resolves([])767 })768 it('calls cache.getProjectRoots', () => {769 return ProjectE2E.paths().then((ret) => {770 expect(ret).to.deep.eq([])771 expect(cache.getProjectRoots).to.be.calledOnce772 })773 })774 })775 context('.getPathsAndIds', () => {776 beforeEach(() => {777 sinon.stub(cache, 'getProjectRoots').resolves([778 '/path/to/first',779 '/path/to/second',780 ])781 sinon.stub(settings, 'id').resolves('id-123')782 })783 it('returns array of objects with paths and ids', () => {784 return ProjectE2E.getPathsAndIds().then((pathsAndIds) => {785 expect(pathsAndIds).to.eql([786 {787 path: '/path/to/first',788 id: 'id-123',789 },790 {791 path: '/path/to/second',792 id: 'id-123',793 },794 ])795 })796 })797 })798 context('.getProjectStatuses', () => {799 beforeEach(() => {800 sinon.stub(user, 'ensureAuthToken').resolves('auth-token-123')801 })802 it('gets projects from api', () => {803 sinon.stub(api, 'getProjects').resolves([])804 return ProjectE2E.getProjectStatuses([])805 .then(() => {806 expect(api.getProjects).to.have.been.calledWith('auth-token-123')807 })808 })809 it('returns array of projects', () => {810 sinon.stub(api, 'getProjects').resolves([])811 return ProjectE2E.getProjectStatuses([])812 .then((projectsWithStatuses) => {813 expect(projectsWithStatuses).to.eql([])814 })815 })816 it('returns same number as client projects, even if there are less api projects', () => {817 sinon.stub(api, 'getProjects').resolves([])818 return ProjectE2E.getProjectStatuses([{}])819 .then((projectsWithStatuses) => {820 expect(projectsWithStatuses.length).to.eql(1)821 })822 })823 it('returns same number as client projects, even if there are more api projects', () => {824 sinon.stub(api, 'getProjects').resolves([{}, {}])825 return ProjectE2E.getProjectStatuses([{}])826 .then((projectsWithStatuses) => {827 expect(projectsWithStatuses.length).to.eql(1)828 })829 })830 it('merges in details of matching projects', () => {831 sinon.stub(api, 'getProjects').resolves([832 { id: 'id-123', lastBuildStatus: 'passing' },833 ])834 return ProjectE2E.getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])835 .then((projectsWithStatuses) => {836 expect(projectsWithStatuses[0]).to.eql({837 id: 'id-123',838 path: '/_test-output/path/to/project',839 lastBuildStatus: 'passing',840 state: 'VALID',841 })842 })843 })844 it('returns client project when it has no id', () => {845 sinon.stub(api, 'getProjects').resolves([])846 return ProjectE2E.getProjectStatuses([{ path: '/_test-output/path/to/project' }])847 .then((projectsWithStatuses) => {848 expect(projectsWithStatuses[0]).to.eql({849 path: '/_test-output/path/to/project',850 state: 'VALID',851 })852 })853 })854 describe('when client project has id and there is no matching user project', () => {855 beforeEach(() => {856 sinon.stub(api, 'getProjects').resolves([])857 })858 it('marks project as invalid if api 404s', () => {859 sinon.stub(api, 'getProject').rejects({ name: '', message: '', statusCode: 404 })860 return ProjectE2E.getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])861 .then((projectsWithStatuses) => {862 expect(projectsWithStatuses[0]).to.eql({863 id: 'id-123',864 path: '/_test-output/path/to/project',865 state: 'INVALID',866 })867 })868 })869 it('marks project as unauthorized if api 403s', () => {870 sinon.stub(api, 'getProject').rejects({ name: '', message: '', statusCode: 403 })871 return ProjectE2E.getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])872 .then((projectsWithStatuses) => {873 expect(projectsWithStatuses[0]).to.eql({874 id: 'id-123',875 path: '/_test-output/path/to/project',876 state: 'UNAUTHORIZED',877 })878 })879 })880 it('merges in project details and marks valid if somehow project exists and is authorized', () => {881 sinon.stub(api, 'getProject').resolves({ id: 'id-123', lastBuildStatus: 'passing' })882 return ProjectE2E.getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])883 .then((projectsWithStatuses) => {884 expect(projectsWithStatuses[0]).to.eql({885 id: 'id-123',886 path: '/_test-output/path/to/project',887 lastBuildStatus: 'passing',888 state: 'VALID',889 })890 })891 })892 it('throws error if not accounted for', () => {893 const error = { name: '', message: '' }894 sinon.stub(api, 'getProject').rejects(error)895 return ProjectE2E.getProjectStatuses([{ id: 'id-123', path: '/_test-output/path/to/project' }])896 .then(() => {897 throw new Error('should have caught error but did not')898 }).catch((err) => {899 expect(err).to.equal(error)900 })901 })902 })903 })904 context('.getProjectStatus', () => {905 beforeEach(function () {906 this.clientProject = {907 id: 'id-123',908 path: '/_test-output/path/to/project',909 }910 sinon.stub(user, 'ensureAuthToken').resolves('auth-token-123')911 })912 it('gets project from api', function () {913 sinon.stub(api, 'getProject').resolves([])914 return ProjectE2E.getProjectStatus(this.clientProject)915 .then(() => {916 expect(api.getProject).to.have.been.calledWith('id-123', 'auth-token-123')917 })918 })919 it('returns project merged with details', function () {920 sinon.stub(api, 'getProject').resolves({921 lastBuildStatus: 'passing',922 })923 return ProjectE2E.getProjectStatus(this.clientProject)924 .then((project) => {925 expect(project).to.eql({926 id: 'id-123',927 path: '/_test-output/path/to/project',928 lastBuildStatus: 'passing',929 state: 'VALID',930 })931 })932 })933 it('returns project, marked as valid, if it does not have an id, without querying api', function () {934 sinon.stub(api, 'getProject')935 this.clientProject.id = undefined936 return ProjectE2E.getProjectStatus(this.clientProject)937 .then((project) => {938 expect(project).to.eql({939 id: undefined,940 path: '/_test-output/path/to/project',941 state: 'VALID',942 })943 expect(api.getProject).not.to.be.called944 })945 })946 it('marks project as invalid if api 404s', function () {947 sinon.stub(api, 'getProject').rejects({ name: '', message: '', statusCode: 404 })948 return ProjectE2E.getProjectStatus(this.clientProject)949 .then((project) => {950 expect(project).to.eql({951 id: 'id-123',952 path: '/_test-output/path/to/project',953 state: 'INVALID',954 })955 })956 })957 it('marks project as unauthorized if api 403s', function () {958 sinon.stub(api, 'getProject').rejects({ name: '', message: '', statusCode: 403 })959 return ProjectE2E.getProjectStatus(this.clientProject)960 .then((project) => {961 expect(project).to.eql({962 id: 'id-123',963 path: '/_test-output/path/to/project',964 state: 'UNAUTHORIZED',965 })966 })967 })968 it('throws error if not accounted for', function () {969 const error = { name: '', message: '' }970 sinon.stub(api, 'getProject').rejects(error)971 return ProjectE2E.getProjectStatus(this.clientProject)972 .then(() => {973 throw new Error('should have caught error but did not')974 }).catch((err) => {975 expect(err).to.equal(error)976 })977 })978 })979 context('.getSecretKeyByPath', () => {980 beforeEach(() => {981 sinon.stub(user, 'ensureAuthToken').resolves('auth-token-123')982 })983 it('calls api.getProjectToken with id + session', function () {984 sinon.stub(api, 'getProjectToken')985 .withArgs(this.projectId, 'auth-token-123')986 .resolves('key-123')987 return ProjectE2E.getSecretKeyByPath(this.todosPath).then((key) => {988 expect(key).to.eq('key-123')989 })990 })991 it('throws CANNOT_FETCH_PROJECT_TOKEN on error', function () {992 sinon.stub(api, 'getProjectToken')993 .withArgs(this.projectId, 'auth-token-123')994 .rejects(new Error())995 return ProjectE2E.getSecretKeyByPath(this.todosPath)996 .then(() => {997 throw new Error('should have caught error but did not')998 }).catch((err) => {999 expect(err.type).to.eq('CANNOT_FETCH_PROJECT_TOKEN')1000 })1001 })1002 })1003 context('.generateSecretKeyByPath', () => {1004 beforeEach(() => {1005 sinon.stub(user, 'ensureAuthToken').resolves('auth-token-123')1006 })1007 it('calls api.updateProjectToken with id + session', function () {1008 sinon.stub(api, 'updateProjectToken')1009 .withArgs(this.projectId, 'auth-token-123')1010 .resolves('new-key-123')1011 return ProjectE2E.generateSecretKeyByPath(this.todosPath).then((key) => {1012 expect(key).to.eq('new-key-123')1013 })1014 })1015 it('throws CANNOT_CREATE_PROJECT_TOKEN on error', function () {1016 sinon.stub(api, 'updateProjectToken')1017 .withArgs(this.projectId, 'auth-token-123')1018 .rejects(new Error())1019 return ProjectE2E.generateSecretKeyByPath(this.todosPath)1020 .then(() => {1021 throw new Error('should have caught error but did not')1022 }).catch((err) => {1023 expect(err.type).to.eq('CANNOT_CREATE_PROJECT_TOKEN')1024 })1025 })1026 })...
electronApp.js
Source:electronApp.js
1// eslint-disable-next-line no-unused-vars2import regeneratorRuntime from 'regenerator-runtime/runtime';3import asar from 'asar';4import assignIn from 'lodash/assignIn';5import _ from 'lodash';6import { LocalInstaller, progress } from 'install-local';7import { transformFileSync } from '@babel/core';8import crypto from 'crypto';9import del from 'del';10import presetEnv from '@babel/preset-env';11import fs from 'fs';12import path from 'path';13import shell from 'shelljs';14import semver from 'semver';15import uglify from 'terser';16import Log from './log';17import ElectronAppScaffold from './electronAppScaffold';18import DependenciesManager from './dependenciesManager';19import BinaryModuleDetector from './binaryModulesDetector';20shell.config.fatal = true;21/**22 * Represents the .desktop dir scaffold.23 * @class24 */25export default class ElectronApp {26 /**27 * @param {MeteorDesktop} $ - context28 * @constructor29 */30 constructor($) {31 this.log = new Log('electronApp');32 this.scaffold = new ElectronAppScaffold($);33 this.depsManager = new DependenciesManager(34 $,35 this.scaffold.getDefaultPackageJson().dependencies36 );37 this.$ = $;38 this.meteorApp = this.$.meteorApp;39 this.packageJson = null;40 this.version = null;41 this.compatibilityVersion = null;42 this.deprectatedPlugins = ['meteor-desktop-localstorage'];43 }44 /**45 * Makes an app.asar from the skeleton app.46 * @property {Array} excludeFromDel - list of paths to exclude from deleting47 * @returns {Promise}48 */49 packSkeletonToAsar(excludeFromDel = []) {50 this.log.info('packing skeleton app and node_modules to asar archive');51 return new Promise((resolve) => {52 const extract = this.getModulesToExtract();53 // We want to pack skeleton app and node_modules together, so we need to temporarily54 // move node_modules to app dir.55 this.log.debug('moving node_modules to app dir');56 fs.renameSync(57 this.$.env.paths.electronApp.nodeModules,58 path.join(this.$.env.paths.electronApp.appRoot, 'node_modules')59 );60 let extracted = false;61 extracted = this.extractModules(extract);62 this.log.debug('packing');63 asar.createPackage(64 this.$.env.paths.electronApp.appRoot,65 this.$.env.paths.electronApp.appAsar,66 () => {67 // Lets move the node_modules back.68 this.log.debug('moving node_modules back from app dir');69 shell.mv(70 path.join(this.$.env.paths.electronApp.appRoot, 'node_modules'),71 this.$.env.paths.electronApp.nodeModules72 );73 if (extracted) {74 // We need to create a full node modules back. In other words we want75 // the extracted modules back.76 extract.forEach(module => shell.cp(77 '-rf',78 path.join(this.$.env.paths.electronApp.extractedNodeModules, module),79 path.join(this.$.env.paths.electronApp.nodeModules, module)80 ));81 // Get the .bin back.82 if (this.$.utils.exists(83 this.$.env.paths.electronApp.extractedNodeModulesBin84 )) {85 shell.cp(86 path.join(this.$.env.paths.electronApp.extractedNodeModulesBin, '*'),87 path.join(this.$.env.paths.electronApp.nodeModules, '.bin')88 );89 }90 }91 this.log.debug('deleting source files');92 const exclude = [this.$.env.paths.electronApp.nodeModules].concat(93 [94 this.$.env.paths.electronApp.appAsar,95 this.$.env.paths.electronApp.packageJson96 ],97 excludeFromDel98 );99 del.sync(100 [`${this.$.env.paths.electronApp.root}${path.sep}*`].concat(101 exclude.map(pathToExclude => `!${pathToExclude}`)102 ),103 { force: true }104 );105 resolve();106 }107 );108 });109 }110 /**111 * Moves specified node modules to a separate directory.112 * @param {Array} extract113 * @returns {boolean}114 */115 extractModules(extract) {116 const ext = ['.js', '.bat', '.sh', '.cmd', ''];117 if (extract.length > 0) {118 if (this.$.utils.exists(this.$.env.paths.electronApp.extractedNodeModules)) {119 shell.rm('-rf', this.$.env.paths.electronApp.extractedNodeModules);120 }121 fs.mkdirSync(this.$.env.paths.electronApp.extractedNodeModules);122 fs.mkdirSync(this.$.env.paths.electronApp.extractedNodeModulesBin);123 extract.forEach((module) => {124 fs.renameSync(125 path.join(this.$.env.paths.electronApp.appRoot, 'node_modules', module),126 path.join(this.$.env.paths.electronApp.extractedNodeModules, module),127 );128 // Move bins.129 this.extractBin(module, ext);130 });131 return true;132 }133 return false;134 }135 /**136 * Extracts the bin files associated with a certain node modules.137 *138 * @param module139 * @param ext140 */141 extractBin(module, ext) {142 let packageJson;143 try {144 packageJson = JSON.parse(145 fs.readFileSync(146 path.join(147 this.$.env.paths.electronApp.extractedNodeModules, module, 'package.json'148 ),149 'utf8'150 )151 );152 } catch (e) {153 packageJson = {};154 }155 const bins = ('bin' in packageJson && typeof packageJson.bin === 'object') ? Object.keys(packageJson.bin) : [];156 if (bins.length > 0) {157 bins.forEach((bin) => {158 ext.forEach((extension) => {159 const binFilePath = path.join(160 this.$.env.paths.electronApp.appRoot,161 'node_modules',162 '.bin',163 `${bin}${extension}`164 );165 if (this.$.utils.exists(binFilePath) ||166 this.$.utils.symlinkExists(binFilePath)167 ) {168 fs.renameSync(169 binFilePath,170 path.join(171 this.$.env.paths.electronApp.extractedNodeModulesBin,172 `${bin}${extension}`173 )174 );175 }176 });177 });178 }179 }180 /**181 * Merges the `extract` field with automatically detected modules.182 */183 getModulesToExtract() {184 const binaryModulesDetector =185 new BinaryModuleDetector(this.$.env.paths.electronApp.nodeModules);186 const toBeExtracted = binaryModulesDetector.detect();187 let { extract } = this.$.desktop.getSettings();188 if (!Array.isArray(extract)) {189 extract = [];190 }191 const merge = {};192 toBeExtracted.concat(extract).forEach((module) => {193 merge[module] = true;194 });195 extract = Object.keys(merge);196 if (extract.length > 0) {197 this.log.verbose(`resultant modules to extract list is: ${extract.join(', ')}`);198 }199 return extract;200 }201 /**202 * Calculates a md5 from all dependencies.203 */204 calculateCompatibilityVersion() {205 this.log.verbose('calculating compatibility version');206 const settings = this.$.desktop.getSettings();207 if (('desktopHCPCompatibilityVersion' in settings)) {208 this.compatibilityVersion = `${settings.desktopHCPCompatibilityVersion}`;209 this.log.warn(`compatibility version overridden to ${this.compatibilityVersion}`);210 return;211 }212 const md5 = crypto.createHash('md5');213 let dependencies = this.depsManager.getDependencies();214 const dependenciesSorted = Object.keys(dependencies).sort();215 dependencies = dependenciesSorted.map(dependency =>216 `${dependency}:${dependencies[dependency]}`);217 const mainCompatibilityVersion = this.$.getVersion().split('.');218 this.log.debug('meteor-desktop compatibility version is ',219 `${mainCompatibilityVersion[0]}`);220 dependencies.push(221 `meteor-desktop:${mainCompatibilityVersion[0]}`222 );223 const desktopCompatibilityVersion = settings.version.split('.')[0];224 this.log.debug('.desktop compatibility version is ', desktopCompatibilityVersion);225 dependencies.push(226 `desktop-app:${desktopCompatibilityVersion}`227 );228 if (process.env.METEOR_DESKTOP_DEBUG_DESKTOP_COMPATIBILITY_VERSION ||229 process.env.METEOR_DESKTOP_DEBUG230 ) {231 this.log.debug(`compatibility version calculated from ${JSON.stringify(dependencies)}`);232 }233 md5.update(JSON.stringify(dependencies));234 this.compatibilityVersion = md5.digest('hex');235 }236 async init() {237 try {238 await this.$.electron.init();239 await this.$.electronBuilder.init();240 } catch (e) {241 this.log.warn('error occurred while initialising electron and electron-builder integration', e);242 process.exit(1);243 }244 }245 /**246 * Runs all necessary tasks to build the desktopified app.247 */248 async build(run = false) {249 // TODO: refactor to a task runner250 this.log.info('scaffolding');251 if (!this.$.desktop.check()) {252 if (!this.$.env.options.scaffold) {253 this.log.error('seems that you do not have a .desktop dir in your project or it is' +254 ' corrupted. Run \'npm run desktop -- init\' to get a new one.');255 // Do not fail, so that npm will not print his error stuff to console.256 process.exit(0);257 } else {258 this.$.desktop.scaffold();259 this.$.meteorApp.updateGitIgnore();260 }261 }262 await this.init();263 try {264 this.$.meteorApp.updateGitIgnore();265 } catch (e) {266 this.log.warn(`error occurred while adding ${this.$.env.paths.electronApp.rootName}` +267 'to .gitignore: ', e);268 }269 try {270 await this.$.meteorApp.removeDeprecatedPackages();271 } catch (e) {272 this.log.error('error while removing deprecated packages: ', e);273 process.exit(1);274 }275 try {276 await this.$.meteorApp.ensureDesktopHCPPackages();277 } catch (e) {278 this.log.error('error while checking for required packages: ', e);279 process.exit(1);280 }281 try {282 await this.scaffold.make();283 } catch (e) {284 this.log.error('error while scaffolding: ', e);285 process.exit(1);286 }287 try {288 const fileName = '.npmrc';289 const dirName = '.meteor/desktop-build';290 if (fs.existsSync(dirName) && fs.existsSync(fileName)) {291 fs.copyFileSync(fileName, `${dirName}/${fileName}`);292 }293 } catch (e) {294 this.log.warn('error while copying .npmrc', e);295 }296 try {297 await this.exposeElectronModules();298 } catch (e) {299 this.log.error('error while exposing electron modules: ', e);300 process.exit(1);301 }302 try {303 this.updatePackageJsonFields();304 } catch (e) {305 this.log.error('error while updating package.json: ', e);306 }307 try {308 this.updateDependenciesList();309 } catch (e) {310 this.log.error('error while merging dependencies list: ', e);311 }312 try {313 this.calculateCompatibilityVersion();314 } catch (e) {315 this.log.error('error while calculating compatibility version: ', e);316 process.exit(1);317 }318 try {319 await this.handleTemporaryNodeModules();320 } catch (e) {321 this.log.error('error occurred while handling temporary node_modules: ', e);322 process.exit(1);323 }324 let nodeModulesRemoved;325 try {326 nodeModulesRemoved = await this.handleStateOfNodeModules();327 } catch (e) {328 this.log.error('error occurred while clearing node_modules: ', e);329 process.exit(1);330 }331 try {332 await this.rebuildDeps(true);333 } catch (e) {334 this.log.error('error occurred while installing node_modules: ', e);335 process.exit(1);336 }337 if (!nodeModulesRemoved) {338 try {339 await this.rebuildDeps();340 } catch (e) {341 this.log.error('error occurred while rebuilding native node modules: ', e);342 process.exit(1);343 }344 }345 try {346 await this.linkNpmPackages();347 } catch (e) {348 this.log.error(`linking packages failed: ${e}`);349 process.exit(1);350 }351 try {352 await this.installLocalNodeModules();353 } catch (e) {354 this.log.error('error occurred while installing local node modules: ', e);355 process.exit(1);356 }357 try {358 await this.ensureMeteorDependencies();359 } catch (e) {360 this.log.error('error occurred while ensuring meteor dependencies are installed: ', e);361 process.exit(1);362 }363 if (this.$.env.isProductionBuild()) {364 try {365 await this.packSkeletonToAsar();366 } catch (e) {367 this.log.error('error while packing skeleton to asar: ', e);368 process.exit(1);369 }370 }371 // TODO: find a way to avoid copying .desktop to a temp location372 try {373 this.copyDesktopToDesktopTemp();374 } catch (e) {375 this.log.error('error while copying .desktop to a temporary location: ', e);376 process.exit(1);377 }378 try {379 await this.updateSettingsJsonFields();380 } catch (e) {381 this.log.error('error while updating settings.json: ', e);382 process.exit(1);383 }384 try {385 await this.excludeFilesFromArchive();386 } catch (e) {387 this.log.error('error while excluding files from packing to asar: ', e);388 process.exit(1);389 }390 try {391 await this.transpileAndMinify();392 } catch (e) {393 this.log.error('error while transpiling or minifying: ', e);394 }395 try {396 await this.packDesktopToAsar();397 } catch (e) {398 this.log.error('error occurred while packing .desktop to asar: ', e);399 process.exit(1);400 }401 try {402 await this.getMeteorClientBuild();403 } catch (e) {404 this.log.error('error occurred during getting meteor mobile build: ', e);405 }406 if (run) {407 this.log.info('running');408 this.$.electron.run();409 } else {410 this.log.info('built');411 }412 }413 /**414 * Copies the `exposedModules` setting from `settings.json` into `preload.js` modifying its code415 * so that the script will have it hardcoded.416 */417 exposeElectronModules() {418 const { exposedModules } = this.$.desktop.getSettings();419 if (exposedModules && Array.isArray(exposedModules) && exposedModules.length > 0) {420 let preload = fs.readFileSync(this.$.env.paths.electronApp.preload, 'utf8');421 const modules = this.$.desktop.getSettings()422 .exposedModules423 .reduce(424 // eslint-disable-next-line no-return-assign,no-param-reassign425 (prev, module) => (prev += `'${module}', `, prev), ''426 );427 preload = preload.replace('const exposedModules = [', `const exposedModules = [${modules}`);428 fs.writeFileSync(this.$.env.paths.electronApp.preload, preload);429 }430 }431 /**432 * Ensures all required dependencies are added to the Meteor project.433 * @returns {Promise.<void>}434 */435 async ensureMeteorDependencies() {436 let packages = [];437 const packagesWithVersion = [];438 let plugins = 'plugins [';439 Object.keys(this.$.desktop.getDependencies().plugins).forEach((plugin) => {440 // Read package.json of the plugin.441 const packageJson =442 JSON.parse(443 fs.readFileSync(444 path.join(445 this.$.env.paths.electronApp.nodeModules, plugin, 'package.json'446 ),447 'utf8'448 )449 );450 if ('meteorDependencies' in packageJson && typeof packageJson.meteorDependencies === 'object') {451 plugins += `${plugin}, `;452 packages.unshift(...Object.keys(packageJson.meteorDependencies));453 packagesWithVersion.unshift(...packages.map((packageName) => {454 if (packageJson.meteorDependencies[packageName] === '@version') {455 return `${packageName}@${packageJson.version}`;456 }457 return `${packageName}@${packageJson.meteorDependencies[packageName]}`;458 }));459 }460 });461 const packagesCount = packages.length;462 packages = packages.filter(value => !this.deprectatedPlugins.includes(value));463 if (packagesCount !== packages.length) {464 this.log.warn('you have some deprecated meteor desktop plugins in your settings, please remove ' +465 `them (deprecated plugins: ${this.deprectatedPlugins.join(', ')})`);466 }467 if (packages.length > 0) {468 plugins = `${plugins.substr(0, plugins.length - 2)}]`;469 try {470 await this.$.meteorApp.meteorManager.ensurePackages(471 packages, packagesWithVersion, plugins472 );473 } catch (e) {474 throw new Error(e);475 }476 }477 }478 /**479 * Builds meteor app.480 */481 async getMeteorClientBuild() {482 await this.$.meteorApp.build();483 }484 /**485 * Removes node_modules if needed.486 * @returns {Promise<void>}487 */488 async handleStateOfNodeModules() {489 if (this.$.env.isProductionBuild() || this.$.env.options.ia32) {490 if (!this.$.env.isProductionBuild()) {491 this.log.info('clearing node_modules because we need to have it clear for ia32 rebuild');492 } else {493 this.log.info('clearing node_modules because this is a production build');494 }495 try {496 await this.$.utils.rmWithRetries(497 '-rf', this.$.env.paths.electronApp.nodeModules498 );499 } catch (e) {500 throw new Error(e);501 }502 return true;503 }504 return false;505 }506 /**507 * If there is a temporary node_modules folder and no node_modules folder, we will508 * restore it, as it might be a leftover from an interrupted flow.509 * @returns {Promise<void>}510 */511 async handleTemporaryNodeModules() {512 if (this.$.utils.exists(this.$.env.paths.electronApp.tmpNodeModules)) {513 if (!this.$.utils.exists(this.$.env.paths.electronApp.nodeModules)) {514 this.log.debug('moving temp node_modules back');515 shell.mv(516 this.$.env.paths.electronApp.tmpNodeModules,517 this.$.env.paths.electronApp.nodeModules518 );519 } else {520 // If there is a node_modules folder, we should clear the temporary one.521 this.log.debug('clearing temp node_modules because new one is already created');522 try {523 await this.$.utils.rmWithRetries(524 '-rf', this.$.env.paths.electronApp.tmpNodeModules525 );526 } catch (e) {527 throw new Error(e);528 }529 }530 }531 }532 /**533 * Runs npm link for every package specified in settings.json->linkPackages.534 */535 async linkNpmPackages() {536 if (this.$.env.isProductionBuild()) {537 return;538 }539 const settings = this.$.desktop.getSettings();540 const promises = [];541 if ('linkPackages' in this.$.desktop.getSettings()) {542 if (Array.isArray(settings.linkPackages)) {543 settings.linkPackages.forEach(packageName =>544 promises.push(545 this.$.meteorApp.runNpm(546 ['link', packageName],547 undefined,548 this.$.env.paths.electronApp.root549 )550 )551 );552 }553 }554 await Promise.all(promises);555 }556 /**557 * Runs npm in the electron app to get the dependencies installed.558 * @returns {Promise}559 */560 async ensureDeps() {561 this.log.info('installing dependencies');562 if (this.$.utils.exists(this.$.env.paths.electronApp.nodeModules)) {563 this.log.debug('running npm prune to wipe unneeded dependencies');564 try {565 await this.runNpm(['prune']);566 } catch (e) {567 throw new Error(e);568 }569 }570 try {571 await this.runNpm(['install'], this.$.env.stdio);572 } catch (e) {573 throw new Error(e);574 }575 }576 /**577 * Warns if plugins version are outdated in compare to the newest scaffold.578 * @param {Object} pluginsVersions - current plugins versions from settings.json579 */580 checkPluginsVersion(pluginsVersions) {581 const settingsJson = JSON.parse(582 fs.readFileSync(path.join(this.$.env.paths.scaffold, 'settings.json'))583 );584 const scaffoldPluginsVersion = this.$.desktop.getDependencies(settingsJson, false).plugins;585 Object.keys(pluginsVersions).forEach((pluginName) => {586 if (pluginName in scaffoldPluginsVersion &&587 scaffoldPluginsVersion[pluginName] !== pluginsVersions[pluginName] &&588 semver.lt(pluginsVersions[pluginName], scaffoldPluginsVersion[pluginName])589 ) {590 this.log.warn(`you are using outdated version ${pluginsVersions[pluginName]} of ` +591 `${pluginName}, the suggested version to use is ` +592 `${scaffoldPluginsVersion[pluginName]}`);593 }594 });595 }596 /**597 * Merges core dependency list with the dependencies from .desktop.598 */599 updateDependenciesList() {600 this.log.info('updating list of package.json\'s dependencies');601 const desktopDependencies = this.$.desktop.getDependencies();602 this.checkPluginsVersion(desktopDependencies.plugins);603 this.log.debug('merging settings.json[dependencies]');604 this.depsManager.mergeDependencies(605 'settings.json[dependencies]',606 desktopDependencies.fromSettings607 );608 this.log.debug('merging settings.json[plugins]');609 this.depsManager.mergeDependencies(610 'settings.json[plugins]',611 desktopDependencies.plugins612 );613 this.log.debug('merging dependencies from modules');614 Object.keys(desktopDependencies.modules).forEach(module =>615 this.depsManager.mergeDependencies(616 `module[${module}]`,617 desktopDependencies.modules[module]618 ));619 this.packageJson.dependencies = this.depsManager.getRemoteDependencies();620 this.packageJson.localDependencies = this.depsManager.getLocalDependencies();621 this.log.debug('writing updated package.json');622 fs.writeFileSync(623 this.$.env.paths.electronApp.packageJson, JSON.stringify(this.packageJson, null, 2)624 );625 }626 /**627 * Install node modules from local paths using local-install.628 *629 * @param {string} arch630 * @returns {Promise}631 */632 installLocalNodeModules(arch = this.$.env.options.ia32 || process.arch === 'ia32' ? 'ia32' : 'x64') {633 const localDependencies = _.values(this.packageJson.localDependencies);634 if (localDependencies.length === 0) {635 return Promise.resolve();636 }637 this.log.info('installing local node modules');638 const lastRebuild = this.$.electronBuilder.prepareLastRebuildObject(arch);639 const env = this.$.electronBuilder.getGypEnv(lastRebuild.frameworkInfo, lastRebuild.platform, lastRebuild.arch);640 const installer = new LocalInstaller(641 { [this.$.env.paths.electronApp.root]: localDependencies },642 { npmEnv: env }643 );644 progress(installer);645 return installer.install();646 }647 /**648 * Rebuild binary dependencies against Electron's node headers.649 * @returns {Promise}650 */651 rebuildDeps(install = false) {652 if (install) {653 this.log.info('issuing node_modules install from electron-builder');654 } else {655 this.log.info('issuing native modules rebuild from electron-builder');656 }657 const arch = this.$.env.options.ia32 || process.arch === 'ia32' ? 'ia32' : 'x64';658 if (this.$.env.options.ia32) {659 this.log.verbose('forcing rebuild for 32bit');660 } else {661 this.log.verbose(`rebuilding for ${arch}`);662 }663 return this.$.electronBuilder.installOrRebuild(arch, undefined, install);664 }665 /**666 * Update package.json fields accordingly to what is set in settings.json.667 *668 * packageJson.name = settings.projectName669 * packageJson.version = settings.version670 * packageJson.* = settings.packageJsonFields671 */672 updatePackageJsonFields() {673 this.log.verbose('updating package.json fields');674 const settings = this.$.desktop.getSettings();675 /** @type {desktopSettings} */676 const packageJson = this.scaffold.getDefaultPackageJson();677 packageJson.version = settings.version;678 if ('packageJsonFields' in settings) {679 assignIn(packageJson, settings.packageJsonFields);680 }681 assignIn(packageJson, { name: settings.projectName });682 this.log.debug('writing updated package.json');683 fs.writeFileSync(684 this.$.env.paths.electronApp.packageJson, JSON.stringify(packageJson, null, 4)685 );686 this.packageJson = packageJson;687 }688 /**689 * Updates settings.json with env (prod/dev) information and versions.690 */691 async updateSettingsJsonFields() {692 this.log.debug('updating settings.json fields');693 const settings = this.$.desktop.getSettings();694 // Save versions.695 settings.compatibilityVersion = this.compatibilityVersion;696 // Pass information about build type to the settings.json.697 settings.env = (this.$.env.isProductionBuild()) ?698 'prod' : 'dev';699 const version = await this.$.desktop.getHashVersion();700 settings.desktopVersion = `${version}_${settings.env}`;701 settings.meteorDesktopVersion = this.$.getVersion();702 if (this.$.env.options.prodDebug) {703 settings.prodDebug = true;704 }705 fs.writeFileSync(706 this.$.env.paths.desktopTmp.settings, JSON.stringify(settings, null, 4)707 );708 }709 /**710 * Copies files from prepared .desktop to desktop.asar in electron app.711 */712 packDesktopToAsar() {713 this.log.info('packing .desktop to asar');714 return new Promise((resolve, reject) => {715 asar.createPackage(716 this.$.env.paths.desktopTmp.root,717 this.$.env.paths.electronApp.desktopAsar,718 () => {719 this.log.verbose('clearing temporary .desktop');720 this.$.utils721 .rmWithRetries('-rf', this.$.env.paths.desktopTmp.root)722 .then(() => {723 resolve();724 })725 .catch((e) => {726 reject(e);727 });728 resolve();729 }730 );731 });732 }733 /**734 * Makes a temporary copy of .desktop.735 */736 copyDesktopToDesktopTemp() {737 this.log.verbose('copying .desktop to temporary location');738 shell.cp('-rf', this.$.env.paths.desktop.root, this.$.env.paths.desktopTmp.root);739 // Remove test files.740 del.sync([741 path.join(this.$.env.paths.desktopTmp.root, '**', '*.test.js')742 ], { force: true });743 }744 /**745 * Runs babel and uglify over .desktop if requested.746 */747 async transpileAndMinify() {748 this.log.info('transpiling and uglifying');749 const settings = this.$.desktop.getSettings();750 const options = 'uglifyOptions' in settings ? settings.uglifyOptions : {};751 const uglifyingEnabled = 'uglify' in settings && !!settings.uglify;752 const preset = presetEnv({ assertVersion: () => { 0 } }, { targets: { node: '12' } });753 const { data: files } = await this.$.utils.readDir(this.$.env.paths.desktopTmp.root);754 files.forEach((file) => {755 if (file.endsWith('.js')) {756 let { code } = transformFileSync(file, {757 presets: [preset]758 });759 let error;760 if (settings.env === 'prod' && uglifyingEnabled) {761 ({ code, error } = uglify.minify(code, options));762 }763 if (error) {764 throw new Error(error);765 }766 fs.writeFileSync(file, code);767 }768 });769 }770 /**771 * Moves all the files that should not be packed into asar into a safe location which is the772 * 'extracted' dir in the electron app.773 */774 async excludeFilesFromArchive() {775 this.log.info('excluding files from packing');776 // Ensure empty `extracted` dir777 try {778 await this.$.utils.rmWithRetries('-rf', this.$.env.paths.electronApp.extracted);779 } catch (e) {780 throw new Error(e);781 }782 shell.mkdir(this.$.env.paths.electronApp.extracted);783 const configs = this.$.desktop.gatherModuleConfigs();784 // Move files that should not be asar'ed.785 configs.forEach((config) => {786 const moduleConfig = config;787 if ('extract' in moduleConfig) {788 if (!Array.isArray(moduleConfig.extract)) {789 moduleConfig.extract = [moduleConfig.extract];790 }791 moduleConfig.extract.forEach((file) => {792 this.log.debug(`excluding ${file} from ${config.name}`);793 const filePath = path.join(794 this.$.env.paths.desktopTmp.modules, moduleConfig.dirName, file795 );796 const destinationPath = path.join(797 this.$.env.paths.electronApp.extracted, moduleConfig.dirName798 );799 if (!this.$.utils.exists(destinationPath)) {800 shell.mkdir(destinationPath);801 }802 shell.mv(filePath, destinationPath);803 });804 }805 });806 }...
scaffold_spec.js
Source:scaffold_spec.js
...241 it('creates pluginsFile when pluginsFolder does not exist', function () {242 // first remove it243 return fs.removeAsync(this.pluginsFolder)244 .then(() => {245 return scaffold.plugins(this.pluginsFolder, this.cfg)246 }).then(() => {247 return fs.readFileAsync(`${this.pluginsFolder}/index.js`, 'utf8')248 }).then((str) => {249 return snapshot(str.split('`').join('<backtick>'))250 })251 })252 it('does not create any files if pluginsFile directory already exists', function () {253 // first remove it254 return fs.removeAsync(this.pluginsFolder)255 .then(() => {256 // create the pluginsFolder ourselves manually257 return fs.ensureDirAsync(this.pluginsFolder)258 }).then(() => {259 // now scaffold260 return scaffold.plugins(this.pluginsFolder, this.cfg)261 }).then(() => {262 return glob('**/*', { cwd: this.pluginsFolder })263 }).then((files) => {264 // ensure no files exist265 expect(files.length).to.eq(0)266 })267 })268 it('does not create any files if pluginsFile is not default', function () {269 this.cfg.resolved.pluginsFile.from = 'config'270 })271 it('does not create any files if pluginsFile is false', function () {272 this.cfg.pluginsFile = false273 return scaffold.plugins(this.pluginsFile, this.cfg)274 .then(() => {275 return glob('**/*', { cwd: this.pluginsFile })276 }).then((files) => {277 expect(files.length).to.eq(0)278 })279 })280 })281 context('.fixture', () => {282 beforeEach(function () {283 const pristinePath = Fixtures.projectPath('pristine')284 return config.get(pristinePath).then((cfg) => {285 this.cfg = cfg;286 ({ fixturesFolder: this.fixturesFolder } = this.cfg)287 })...
scaffold_spec.coffee.js
Source:scaffold_spec.coffee.js
1exports['lib/scaffold .fileTree returns tree-like structure of scaffolded 1'] = [2 {3 "name": "tests",4 "children": [5 {6 "name": "examples",7 "children": [8 {9 "name": "actions.spec.js"10 },11 {12 "name": "aliasing.spec.js"13 },14 {15 "name": "assertions.spec.js"16 },17 {18 "name": "connectors.spec.js"19 },20 {21 "name": "cookies.spec.js"22 },23 {24 "name": "cypress_api.spec.js"25 },26 {27 "name": "files.spec.js"28 },29 {30 "name": "local_storage.spec.js"31 },32 {33 "name": "location.spec.js"34 },35 {36 "name": "misc.spec.js"37 },38 {39 "name": "navigation.spec.js"40 },41 {42 "name": "network_requests.spec.js"43 },44 {45 "name": "querying.spec.js"46 },47 {48 "name": "spies_stubs_clocks.spec.js"49 },50 {51 "name": "traversal.spec.js"52 },53 {54 "name": "utilities.spec.js"55 },56 {57 "name": "viewport.spec.js"58 },59 {60 "name": "waiting.spec.js"61 },62 {63 "name": "window.spec.js"64 }65 ]66 },67 {68 "name": "_fixtures",69 "children": [70 {71 "name": "example.json"72 }73 ]74 },75 {76 "name": "_support",77 "children": [78 {79 "name": "commands.js"80 },81 {82 "name": "index.js"83 }84 ]85 }86 ]87 },88 {89 "name": "cypress",90 "children": [91 {92 "name": "plugins",93 "children": [94 {95 "name": "index.js"96 }97 ]98 }99 ]100 }101]102exports['lib/scaffold .fileTree leaves out fixtures if configured to false 1'] = [103 {104 "name": "tests",105 "children": [106 {107 "name": "examples",108 "children": [109 {110 "name": "actions.spec.js"111 },112 {113 "name": "aliasing.spec.js"114 },115 {116 "name": "assertions.spec.js"117 },118 {119 "name": "connectors.spec.js"120 },121 {122 "name": "cookies.spec.js"123 },124 {125 "name": "cypress_api.spec.js"126 },127 {128 "name": "files.spec.js"129 },130 {131 "name": "local_storage.spec.js"132 },133 {134 "name": "location.spec.js"135 },136 {137 "name": "misc.spec.js"138 },139 {140 "name": "navigation.spec.js"141 },142 {143 "name": "network_requests.spec.js"144 },145 {146 "name": "querying.spec.js"147 },148 {149 "name": "spies_stubs_clocks.spec.js"150 },151 {152 "name": "traversal.spec.js"153 },154 {155 "name": "utilities.spec.js"156 },157 {158 "name": "viewport.spec.js"159 },160 {161 "name": "waiting.spec.js"162 },163 {164 "name": "window.spec.js"165 }166 ]167 },168 {169 "name": "_support",170 "children": [171 {172 "name": "commands.js"173 },174 {175 "name": "index.js"176 }177 ]178 }179 ]180 },181 {182 "name": "cypress",183 "children": [184 {185 "name": "plugins",186 "children": [187 {188 "name": "index.js"189 }190 ]191 }192 ]193 }194]195exports['lib/scaffold .fileTree leaves out support if configured to false 1'] = [196 {197 "name": "tests",198 "children": [199 {200 "name": "examples",201 "children": [202 {203 "name": "actions.spec.js"204 },205 {206 "name": "aliasing.spec.js"207 },208 {209 "name": "assertions.spec.js"210 },211 {212 "name": "connectors.spec.js"213 },214 {215 "name": "cookies.spec.js"216 },217 {218 "name": "cypress_api.spec.js"219 },220 {221 "name": "files.spec.js"222 },223 {224 "name": "local_storage.spec.js"225 },226 {227 "name": "location.spec.js"228 },229 {230 "name": "misc.spec.js"231 },232 {233 "name": "navigation.spec.js"234 },235 {236 "name": "network_requests.spec.js"237 },238 {239 "name": "querying.spec.js"240 },241 {242 "name": "spies_stubs_clocks.spec.js"243 },244 {245 "name": "traversal.spec.js"246 },247 {248 "name": "utilities.spec.js"249 },250 {251 "name": "viewport.spec.js"252 },253 {254 "name": "waiting.spec.js"255 },256 {257 "name": "window.spec.js"258 }259 ]260 },261 {262 "name": "_fixtures",263 "children": [264 {265 "name": "example.json"266 }267 ]268 }269 ]270 },271 {272 "name": "cypress",273 "children": [274 {275 "name": "plugins",276 "children": [277 {278 "name": "index.js"279 }280 ]281 }282 ]283 }284]285exports['lib/scaffold .support creates supportFolder and commands.js and index.js when supportFolder does not exist 1'] = `286// ***********************************************287// This example commands.js shows you how to288// create various custom commands and overwrite289// existing commands.290//291// For more comprehensive examples of custom292// commands please read more here:293// https://on.cypress.io/custom-commands294// ***********************************************295//296//297// -- This is a parent command --298// Cypress.Commands.add("login", (email, password) => { ... })299//300//301// -- This is a child command --302// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })303//304//305// -- This is a dual command --306// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })307//308//309// -- This will overwrite an existing command --310// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })311`312exports['lib/scaffold .support creates supportFolder and commands.js and index.js when supportFolder does not exist 2'] = `313// ***********************************************************314// This example support/index.js is processed and315// loaded automatically before your test files.316//317// This is a great place to put global configuration and318// behavior that modifies Cypress.319//320// You can change the location of this file or turn off321// automatically serving support files with the322// 'supportFile' configuration option.323//324// You can read more here:325// https://on.cypress.io/configuration326// ***********************************************************327// Import commands.js using ES2015 syntax:328import './commands'329// Alternatively you can use CommonJS syntax:330// require('./commands')331`332exports['lib/scaffold .fileTree leaves out plugins if configured to false 1'] = [333 {334 "name": "tests",335 "children": [336 {337 "name": "examples",338 "children": [339 {340 "name": "actions.spec.js"341 },342 {343 "name": "aliasing.spec.js"344 },345 {346 "name": "assertions.spec.js"347 },348 {349 "name": "connectors.spec.js"350 },351 {352 "name": "cookies.spec.js"353 },354 {355 "name": "cypress_api.spec.js"356 },357 {358 "name": "files.spec.js"359 },360 {361 "name": "local_storage.spec.js"362 },363 {364 "name": "location.spec.js"365 },366 {367 "name": "misc.spec.js"368 },369 {370 "name": "navigation.spec.js"371 },372 {373 "name": "network_requests.spec.js"374 },375 {376 "name": "querying.spec.js"377 },378 {379 "name": "spies_stubs_clocks.spec.js"380 },381 {382 "name": "traversal.spec.js"383 },384 {385 "name": "utilities.spec.js"386 },387 {388 "name": "viewport.spec.js"389 },390 {391 "name": "waiting.spec.js"392 },393 {394 "name": "window.spec.js"395 }396 ]397 },398 {399 "name": "_fixtures",400 "children": [401 {402 "name": "example.json"403 }404 ]405 },406 {407 "name": "_support",408 "children": [409 {410 "name": "commands.js"411 },412 {413 "name": "index.js"414 }415 ]416 }417 ]418 }419]420exports['lib/scaffold .plugins creates pluginsFile when pluginsFolder does not exist 1'] = `421// ***********************************************************422// This example plugins/index.js can be used to load plugins423//424// You can change the location of this file or turn off loading425// the plugins file with the 'pluginsFile' configuration option.426//427// You can read more here:428// https://on.cypress.io/plugins-guide429// ***********************************************************430// This function is called when a project is opened or re-opened (e.g. due to431// the project's config changing)432module.exports = (on, config) => {433 // <backtick>on<backtick> is used to hook into various events Cypress emits434 // <backtick>config<backtick> is the resolved Cypress config435}...
release.config.js
Source:release.config.js
1const commitAnalyzerOptions = {2 preset: 'angular',3 releaseRules: [4 { type: 'breaking', release: 'major' },5 { type: 'feat', release: 'minor' },6 { type: 'fix', release: 'patch' },7 { type: 'task', release: 'patch' },8 { type: 'refactor', release: 'patch' },9 { type: 'docs', release: 'patch' },10 { type: 'chore', release: false },11 { scope: 'style', release: false },12 { scope: 'test', release: false },13 { scope: 'deploy', release: false },14 ],15 parserOpts: {16 noteKeywords: [],17 },18}19const releaseNotesGeneratorOptions = {20 writerOpts: {21 transform: (commit, context) => {22 const issues = []23 const types = {24 breaking: 'Breaking',25 feat: 'Features',26 fix: 'Bug Fixes',27 task: 'Task Commit',28 refactor: 'Code Refactoring',29 docs: 'Documentation',30 chore: 'Maintenance',31 }32 commit.type = types[commit.type]33 if (typeof commit.hash === 'string') {34 commit.shortHash = commit.hash.substring(0, 7)35 }36 if (typeof commit.subject === 'string') {37 let url = context.repository ? `${context.host}/${context.owner}/${context.repository}` : context.repoUrl38 if (url) {39 url = `${url}/issues/`40 // Issue URLs.41 commit.subject = commit.subject.replace(/#([0-9]+)/g, (_, issue) => {42 issues.push(issue)43 return `[#${issue}](${url}${issue})`44 })45 }46 if (context.host) {47 // User URLs.48 commit.subject = commit.subject.replace(/\B@([a-z0-9](?:-?[a-z0-9/]){0,38})/g, (_, username) => {49 if (username.includes('/')) {50 return `@${username}`51 }52 return `[@${username}](${context.host}/${username})`53 })54 }55 }56 // remove references that already appear in the subject57 commit.references = commit.references.filter((reference) => {58 if (issues.indexOf(reference.issue) === -1) {59 return true60 }61 return false62 })63 return commit64 },65 },66}67module.exports = {68 debug: true,69 branches: ['main'],70 repositoryUrl: 'https://github.com/jetstreamlabs/scaffold',71 plugins: [72 // analyze commits with conventional-changelog73 ['@semantic-release/commit-analyzer', commitAnalyzerOptions],74 // generate changelog content with conventional-changelog75 ['@semantic-release/release-notes-generator', releaseNotesGeneratorOptions],76 // updates the changelog file77 [78 '@semantic-release/changelog',79 {80 changelogFile: 'CHANGELOG.md',81 changelogTitle: '# Changelog',82 },83 ],84 // creating a new version commit85 [86 '@semantic-release/git',87 {88 assets: ['CHANGELOG.md'],89 },90 ],91 '@semantic-release/github',92 ],...
index.js
Source:index.js
1module.exports = {2 type: "cli-command",3 name: "cli-command-scaffold",4 create({ yargs, context }) {5 yargs.command("scaffold", "Generate boilerplate code", () => {6 return scaffold({ context });7 });8 }9};10const pluginToChoice = plugin => ({11 name: `${plugin.scaffold.name}`,12 value: plugin.name13});14const scaffold = async ({ context }) => {15 const inquirer = require("inquirer");16 const ora = require("ora");17 const scaffoldPlugins = context.plugins.byType("cli-plugin-scaffold-template");18 if (!scaffoldPlugins.length) {19 console.log(20 `ð¨ We couldn't find any scaffolding plugins. Add plugins to your "webiny.root.js" file!`21 );22 process.exit(1);23 }24 const choices = Object.values(scaffoldPlugins).map(pluginToChoice);25 const { selectedPluginName } = await inquirer.prompt({26 type: "list",27 name: "selectedPluginName",28 message: "Pick a template to scaffold",29 choices30 });31 const { scaffold } = context.plugins.byName(selectedPluginName);32 const questions = scaffold.questions;33 const inqQuestions = typeof questions === "function" ? questions({ context }) : questions;34 const input = await inquirer.prompt(inqQuestions);35 const oraSpinner = ora().start(`Generating template...\n`);36 try {37 await scaffold.generate({ input, context, oraSpinner });38 oraSpinner.succeed("Done!");39 } catch (e) {40 oraSpinner.stop();41 console.log(e);42 process.exit(1);43 }...
.eslintrc.ui.js
Source:.eslintrc.ui.js
1const scaffold = require("./.eslintrc");2module.exports = {3 ...scaffold,4 extends: [5 ...scaffold.extends,6 "plugin:@microsoft/sdl/react", // Microsoft SDL React rules7 "plugin:react/recommended",8 "plugin:testing-library/react",9 ],10 ignorePatterns: [...scaffold.ignorePatterns, "creevey.config.js"],11 plugins: [...scaffold.plugins, "react", "react-hooks"],12 rules: {13 ...scaffold.rules,14 "react-hooks/exhaustive-deps": [15 "warn",16 { additionalHooks: "^(useCallbackDelay|useCommands|useStateAsync)$" },17 ],18 "react/sort-prop-types": [19 "error",20 {21 callbacksLast: false,22 ignoreCase: true,23 requiredFirst: true,24 sortShapeProp: true,25 noSortAlphabetically: false,26 },27 ],28 "react/self-closing-comp": "error",29 "react/jsx-boolean-value": "error",30 "react/jsx-closing-bracket-location": "error",31 "react/jsx-closing-tag-location": "error",32 "react/jsx-curly-brace-presence": [33 "warn",34 { props: "never", children: "never" },35 ],36 "react/jsx-wrap-multilines": "error",37 "react/jsx-uses-react": "off", //https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#eslint38 "react/react-in-jsx-scope": "off", //https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#eslint39 "testing-library/prefer-screen-queries": "off",40 },41 settings: {42 react: {43 version: "detect",44 },45 },...
rollup.config.js
Source:rollup.config.js
1import buble from 'rollup-plugin-buble';2import { version } from './package.json';3var banner = `4/**5 * rest-scaffold.js version ${version}6 * MIT License, Copyright (c) 2019 Gregory N. Schmit7 */8`;9export default {10 input: "src/main.js",11 output: {12 banner: banner.trim(),13 file: "dist/rest-scaffold.js",14 format: "umd",15 name: "rest-scaffold"16 },17 plugins: [buble()]...
Using AI Code Generation
1const scaffold = require('cypress-cucumber-scaffold-plugin')2module.exports = (on, config) => {3 on('file:preprocessor', scaffold())4}5{6}7import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'8Given('I open Google\'s search page', () => {9})10When('I type {string} in the search bar', (searchTerm) => {11 cy.get('.gLFyf').type(searchTerm)12})13Then('the search bar should contain {string}', (searchTerm) => {14 cy.get('.gLFyf').should('have.value', searchTerm)15})16const cucumber = require('cypress-cucumber-preprocessor').default17module.exports = (on, config) => {18 on('file:preprocessor', cucumber())19}20{21}
Using AI Code Generation
1describe('My First Test Suite', function() 2{3 it('My FirstTest case', function() {4 cy.title().should('eq','ProtoCommerce')5 cy.get('.brand.greenLogo').should('be.visible')6 cy.get('.search-keyword').should('be.visible')7 cy.get('.search-keyword').should('be.visible')8 cy.get('.search-keyword').type('ca')9 cy.get('.products').as('productLocator')10 cy.get('@productLocator').find('.product').should('have.length',4)11 cy.get('@productLocator').find('.product').eq(2).contains('ADD TO CART').click()12 cy.get('@productLocator').find('.product').each(($el, index, $list) => {13 const vegText = $el.find('h4.product-name').text()14 if(vegText.includes('Cashews'))15 {16 $el.find('button').click()17 }18 })19 cy.get('.cart-icon > img').click()20 cy.contains('PROCEED TO CHECKOUT').click()
Using AI Code Generation
1import scaffold from 'cypress-scaffold-plugin';2describe('test', () => {3 it('should do something', () => {4 scaffold.plugins();5 });6});7describe('test', () => {8 it('should do something', () => {9 });10});11describe('test', () => {12 it('should do something', () => {13 });14});
Cypress is a renowned Javascript-based open-source, easy-to-use end-to-end testing framework primarily used for testing web applications. Cypress is a relatively new player in the automation testing space and has been gaining much traction lately, as evidenced by the number of Forks (2.7K) and Stars (42.1K) for the project. LambdaTest’s Cypress Tutorial covers step-by-step guides that will help you learn from the basics till you run automation tests on LambdaTest.
You can elevate your expertise with end-to-end testing using the Cypress automation framework and stay one step ahead in your career by earning a Cypress certification. Check out our Cypress 101 Certification.
Watch this 3 hours of complete tutorial to learn the basics of Cypress and various Cypress commands with the Cypress testing at LambdaTest.
Get 100 minutes of automation test minutes FREE!!