mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-02 13:00:18 -05:00
internal: Add support for Studio first use instructions (#32197)
* add support for studioFirstUseInstructionsDismissed * add support for setting global or project saved state * add get:app:state socket event * handle getSavedState errors * check that config is initialized
This commit is contained in:
@@ -403,7 +403,8 @@ export class ProjectBase extends EE {
|
||||
onReloadBrowser: options.onReloadBrowser,
|
||||
onFocusTests: options.onFocusTests,
|
||||
onSpecChanged: options.onSpecChanged,
|
||||
onSavedStateChanged: (state: any) => this.saveState(state),
|
||||
getSavedState: this.getSavedState.bind(this),
|
||||
onSavedStateChanged: this.saveState.bind(this),
|
||||
closeExtraTargets: this.closeExtraTargets,
|
||||
|
||||
onStudioInit: async () => {
|
||||
@@ -714,17 +715,27 @@ export class ProjectBase extends EE {
|
||||
|
||||
// Saved state
|
||||
|
||||
// forces saving of project's state by first merging with argument
|
||||
async saveState (stateChanges = {}) {
|
||||
async getSavedState (options: { type: 'global' | 'project' } = { type: 'project' }) {
|
||||
if (!this.cfg) {
|
||||
throw new Error('Missing project config')
|
||||
throw new Error('Missing project config trying to get saved state')
|
||||
}
|
||||
|
||||
if (!this.projectRoot) {
|
||||
const state = await savedState.create(options.type === 'project' ? this.projectRoot : undefined, this.cfg.isTextTerminal)
|
||||
|
||||
return state.get()
|
||||
}
|
||||
|
||||
// forces saving of project's state by first merging with argument
|
||||
async saveState (stateChanges = {}, options: { type: 'global' | 'project' } = { type: 'project' }) {
|
||||
if (!this.cfg) {
|
||||
throw new Error('Missing project config trying to save state')
|
||||
}
|
||||
|
||||
if (options.type === 'project' && !this.projectRoot) {
|
||||
throw new Error('Missing project root')
|
||||
}
|
||||
|
||||
let state = await savedState.create(this.projectRoot, this.cfg.isTextTerminal)
|
||||
let state = await savedState.create(options.type === 'project' ? this.projectRoot : undefined, this.cfg.isTextTerminal)
|
||||
|
||||
state.set(stateChanges)
|
||||
this.cfg.state = await state.get()
|
||||
|
||||
@@ -157,6 +157,7 @@ export class SocketBase {
|
||||
onChromiumRun () {},
|
||||
onReloadBrowser () {},
|
||||
closeExtraTargets () {},
|
||||
getSavedState () {},
|
||||
onSavedStateChanged () {},
|
||||
onTestFileChange () {},
|
||||
onCaptureVideoFrames () {},
|
||||
@@ -581,8 +582,21 @@ export class SocketBase {
|
||||
return cb(s || {}, cachedTestState)
|
||||
})
|
||||
|
||||
socket.on('get:app:state', async (opts, cb) => {
|
||||
try {
|
||||
const state = await options.getSavedState(opts)
|
||||
|
||||
cb({ data: state })
|
||||
} catch (error) {
|
||||
cb({ error: errors.cloneErr(error) })
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('save:app:state', (state, cb) => {
|
||||
options.onSavedStateChanged(state)
|
||||
const opts = state.__options
|
||||
const stateWithoutOptions = _.omit(state, '__options')
|
||||
|
||||
options.onSavedStateChanged(stateWithoutOptions, opts)
|
||||
|
||||
// we only use the 'ack' here in tests
|
||||
if (cb) {
|
||||
|
||||
@@ -80,14 +80,51 @@ describe('lib/project-base', () => {
|
||||
expect(p.projectRoot).to.eq(path.resolve(path.join('..', 'foo', 'bar')))
|
||||
})
|
||||
|
||||
context('#getSavedState', () => {
|
||||
beforeEach(async function () {
|
||||
const globalState = await savedState.create()
|
||||
|
||||
await globalState.remove()
|
||||
await globalState.set({ reporterWidth: 400 })
|
||||
|
||||
const projectState = await savedState.create(this.project.projectRoot)
|
||||
|
||||
await projectState.remove()
|
||||
await projectState.set({ reporterWidth: 500 })
|
||||
})
|
||||
|
||||
it('returns global state when type is global', async function () {
|
||||
const state = await this.project.getSavedState({ type: 'global' })
|
||||
|
||||
expect(state).to.deep.eq({ reporterWidth: 400 })
|
||||
})
|
||||
|
||||
it('returns project state when type is project', async function () {
|
||||
const state = await this.project.getSavedState({ type: 'project' })
|
||||
|
||||
expect(state).to.deep.eq({ reporterWidth: 500 })
|
||||
})
|
||||
|
||||
it('returns project state when type is undefined', async function () {
|
||||
const state = await this.project.getSavedState()
|
||||
|
||||
expect(state).to.deep.eq({ reporterWidth: 500 })
|
||||
})
|
||||
})
|
||||
|
||||
context('#saveState', function () {
|
||||
beforeEach(function () {
|
||||
beforeEach(async function () {
|
||||
const supportFile = path.join('the', 'save', 'state', 'test')
|
||||
|
||||
this.project.cfg = { supportFile }
|
||||
|
||||
return savedState.create(this.project.projectRoot)
|
||||
.then((state) => state.remove())
|
||||
const globalState = await savedState.create()
|
||||
|
||||
await globalState.remove()
|
||||
|
||||
const projectState = await savedState.create(this.project.projectRoot)
|
||||
|
||||
await projectState.remove()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
@@ -119,6 +156,33 @@ describe('lib/project-base', () => {
|
||||
.then(() => this.project.saveState({ appWidth: 'modified' }))
|
||||
.then((state) => expect(state).to.deep.eq({ appWidth: 'modified' }))
|
||||
})
|
||||
|
||||
it('saves global state when type is global', async function () {
|
||||
await this.project.saveState({ reporterWidth: 1 }, { type: 'global' })
|
||||
|
||||
const state = await savedState.create()
|
||||
.then((state) => state.get())
|
||||
|
||||
expect(state).to.deep.eq({ reporterWidth: 1 })
|
||||
})
|
||||
|
||||
it('saves project state when type is project', async function () {
|
||||
await this.project.saveState({ reporterWidth: 2 }, { type: 'project' })
|
||||
|
||||
const state = await savedState.create(this.project.projectRoot)
|
||||
.then((state) => state.get())
|
||||
|
||||
expect(state).to.deep.eq({ reporterWidth: 2 })
|
||||
})
|
||||
|
||||
it('saves project state when type is undefined', async function () {
|
||||
await this.project.saveState({ reporterWidth: 3 })
|
||||
|
||||
const state = await savedState.create(this.project.projectRoot)
|
||||
.then((state) => state.get())
|
||||
|
||||
expect(state).to.deep.eq({ reporterWidth: 3 })
|
||||
})
|
||||
})
|
||||
|
||||
context('#initializeConfig', () => {
|
||||
|
||||
@@ -64,6 +64,7 @@ describe('lib/socket', () => {
|
||||
})
|
||||
.then(() => {
|
||||
this.options = {
|
||||
getSavedState: sinon.stub(),
|
||||
onSavedStateChanged: sinon.spy(),
|
||||
onStudioInit: sinon.stub(),
|
||||
onStudioDestroy: sinon.stub(),
|
||||
@@ -250,12 +251,38 @@ describe('lib/socket', () => {
|
||||
})
|
||||
})
|
||||
|
||||
context('on(save:app:state)', () => {
|
||||
it('calls onSavedStateChanged with the state', function (done) {
|
||||
return this.client.emit('save:app:state', { reporterWidth: 500 }, () => {
|
||||
expect(this.options.onSavedStateChanged).to.be.calledWith({ reporterWidth: 500 })
|
||||
context('on(get:app:state)', () => {
|
||||
it('calls getSavedState with options and returns the state', function (done) {
|
||||
this.options.getSavedState.resolves({ reporterWidth: 500 })
|
||||
|
||||
return done()
|
||||
this.client.emit('get:app:state', { type: 'global' }, (resp) => {
|
||||
expect(this.options.getSavedState).to.be.calledWith({ type: 'global' })
|
||||
expect(resp.data).to.deep.eq({ reporterWidth: 500 })
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('handles errors thrown by getSavedState', function (done) {
|
||||
const err = new Error('boom')
|
||||
|
||||
this.options.getSavedState.rejects(err)
|
||||
|
||||
this.client.emit('get:app:state', { type: 'global' }, (resp) => {
|
||||
expect(this.options.getSavedState).to.be.calledWith({ type: 'global' })
|
||||
expect(resp.error).to.deep.eq(errors.cloneErr(err))
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('on(save:app:state)', () => {
|
||||
it('calls onSavedStateChanged with the state and options', function (done) {
|
||||
this.client.emit('save:app:state', { reporterWidth: 500, __options: { type: 'global' } }, () => {
|
||||
expect(this.options.onSavedStateChanged).to.be.calledWith({ reporterWidth: 500 }, { type: 'global' })
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -51,6 +51,7 @@ export const allowedKeys: Readonly<Array<keyof AllowedState>> = [
|
||||
'notifyWhenRunStarts',
|
||||
'notifyWhenRunStartsFailing',
|
||||
'notifyWhenRunCompletes',
|
||||
'studioFirstUseInstructionsDismissed',
|
||||
] as const
|
||||
|
||||
type Maybe<T> = T | null | undefined
|
||||
@@ -93,4 +94,5 @@ export type AllowedState = Partial<{
|
||||
notifyWhenRunStarts: Maybe<boolean>
|
||||
notifyWhenRunStartsFailing: Maybe<boolean>
|
||||
notifyWhenRunCompletes: Maybe<NotifyWhenRunCompletes[]>
|
||||
studioFirstUseInstructionsDismissed: Maybe<boolean>
|
||||
}>
|
||||
|
||||
Reference in New Issue
Block a user