diff --git a/.github/workflows/size-check.yml b/.github/workflows/size-check.yml index c970b0ea0e0decca7611e3f3566459d7946c46dd..9988fd838e1bfd2983db7d878abe500c1d3bdfd6 100644 --- a/.github/workflows/size-check.yml +++ b/.github/workflows/size-check.yml @@ -20,9 +20,3 @@ jobs: build_script: build files: packages/size-check/dist/size-check.es.js packages/size-check/dist/style.css packages/uni-app/dist/uni-app.es.js packages/uni-h5-vue/dist/vue.runtime.esm.js packages/uni-mp-vue/dist/vue.runtime.esm.js packages/uni-mp-alipay/dist/uni.api.esm.js packages/uni-mp-alipay/dist/uni.mp.esm.js packages/uni-mp-baidu/dist/uni.api.esm.js packages/uni-mp-baidu/dist/uni.mp.esm.js packages/uni-mp-qq/dist/uni.api.esm.js packages/uni-mp-qq/dist/uni.mp.esm.js packages/uni-mp-toutiao/dist/uni.api.esm.js packages/uni-mp-toutiao/dist/uni.mp.esm.js packages/uni-mp-weixin/dist/uni.api.esm.js packages/uni-mp-weixin/dist/uni.mp.esm.js packages/uni-quickapp-webview/dist/uni.api.esm.js packages/uni-quickapp-webview/dist/uni.mp.esm.js - run: npm run test - - uses: bahmutov/npm-install@v1 - with: - working-directory: packages/playground/ssr - - name: build ssr - run: npm run build:ssr - working-directory: packages/playground/ssr diff --git a/.github/workflows/ssr.yml b/.github/workflows/ssr.yml new file mode 100644 index 0000000000000000000000000000000000000000..a72697634e2a825c9080beb8aa05adecf9417241 --- /dev/null +++ b/.github/workflows/ssr.yml @@ -0,0 +1,29 @@ +name: 'ssr' +on: + push: + branches: + - next + pull_request: + branches: + - next +jobs: + cypress-run: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: build + run: npm run build + - uses: bahmutov/npm-install@v1 + working-directory: packages/playground/ssr + - name: Cypress run + uses: cypress-io/github-action@v2 + working-directory: packages/playground/ssr + with: + install: false + start: npm run dev:ssr + wait-on: 'http://localhost:3000' + wait-on-timeout: 120 + browser: chrome + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/package.json b/package.json index e94373c20f378e268b099705d773aa28971d83e8..bbff3918a89abc7c9ee9d4dc115d867b7911625c 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "format": "prettier --write --parser typescript \"packages/**/*.ts?(x)\"", "ls-lint": "ls-lint", "test": "jest", - "preinstall": "node ./scripts/checkYarn.js" + "preinstall": "node ./scripts/checkYarn.js", + "e2e:ssr": "cypress open --project packages/playground/ssr" }, "types": "test-dts/index.d.ts", "tsd": { diff --git a/packages/playground/ssr/cypress.json b/packages/playground/ssr/cypress.json new file mode 100644 index 0000000000000000000000000000000000000000..17ef242e711f0f7671c727133fe03419968bac3b --- /dev/null +++ b/packages/playground/ssr/cypress.json @@ -0,0 +1,3 @@ +{ + "baseUrl": "http://localhost:3000" +} diff --git a/packages/playground/ssr/cypress/examples/actions.spec.js b/packages/playground/ssr/cypress/examples/actions.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..2f11a34565518a013aee31615cda8d296208cc60 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/actions.spec.js @@ -0,0 +1,313 @@ +/// + +context('Actions', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/actions') + }) + + // https://on.cypress.io/interacting-with-elements + + it('.type() - type into a DOM element', () => { + // https://on.cypress.io/type + cy.get('.action-email') + .type('fake@email.com') + .should('have.value', 'fake@email.com') + + // .type() with special character sequences + .type('{leftarrow}{rightarrow}{uparrow}{downarrow}') + .type('{del}{selectall}{backspace}') + + // .type() with key modifiers + .type('{alt}{option}') //these are equivalent + .type('{ctrl}{control}') //these are equivalent + .type('{meta}{command}{cmd}') //these are equivalent + .type('{shift}') + + // Delay each keypress by 0.1 sec + .type('slow.typing@email.com', { delay: 100 }) + .should('have.value', 'slow.typing@email.com') + + cy.get('.action-disabled') + // Ignore error checking prior to type + // like whether the input is visible or disabled + .type('disabled error checking', { force: true }) + .should('have.value', 'disabled error checking') + }) + + it('.focus() - focus on a DOM element', () => { + // https://on.cypress.io/focus + cy.get('.action-focus') + .focus() + .should('have.class', 'focus') + .prev() + .should('have.attr', 'style', 'color: orange;') + }) + + it('.blur() - blur off a DOM element', () => { + // https://on.cypress.io/blur + cy.get('.action-blur') + .type('About to blur') + .blur() + .should('have.class', 'error') + .prev() + .should('have.attr', 'style', 'color: red;') + }) + + it('.clear() - clears an input or textarea element', () => { + // https://on.cypress.io/clear + cy.get('.action-clear') + .type('Clear this text') + .should('have.value', 'Clear this text') + .clear() + .should('have.value', '') + }) + + it('.submit() - submit a form', () => { + // https://on.cypress.io/submit + cy.get('.action-form').find('[type="text"]').type('HALFOFF') + + cy.get('.action-form') + .submit() + .next() + .should('contain', 'Your form has been submitted!') + }) + + it('.click() - click on a DOM element', () => { + // https://on.cypress.io/click + cy.get('.action-btn').click() + + // You can click on 9 specific positions of an element: + // ----------------------------------- + // | topLeft top topRight | + // | | + // | | + // | | + // | left center right | + // | | + // | | + // | | + // | bottomLeft bottom bottomRight | + // ----------------------------------- + + // clicking in the center of the element is the default + cy.get('#action-canvas').click() + + cy.get('#action-canvas').click('topLeft') + cy.get('#action-canvas').click('top') + cy.get('#action-canvas').click('topRight') + cy.get('#action-canvas').click('left') + cy.get('#action-canvas').click('right') + cy.get('#action-canvas').click('bottomLeft') + cy.get('#action-canvas').click('bottom') + cy.get('#action-canvas').click('bottomRight') + + // .click() accepts an x and y coordinate + // that controls where the click occurs :) + + cy.get('#action-canvas') + .click(80, 75) // click 80px on x coord and 75px on y coord + .click(170, 75) + .click(80, 165) + .click(100, 185) + .click(125, 190) + .click(150, 185) + .click(170, 165) + + // click multiple elements by passing multiple: true + cy.get('.action-labels>.label').click({ multiple: true }) + + // Ignore error checking prior to clicking + cy.get('.action-opacity>.btn').click({ force: true }) + }) + + it('.dblclick() - double click on a DOM element', () => { + // https://on.cypress.io/dblclick + + // Our app has a listener on 'dblclick' event in our 'scripts.js' + // that hides the div and shows an input on double click + cy.get('.action-div').dblclick().should('not.be.visible') + cy.get('.action-input-hidden').should('be.visible') + }) + + it('.rightclick() - right click on a DOM element', () => { + // https://on.cypress.io/rightclick + + // Our app has a listener on 'contextmenu' event in our 'scripts.js' + // that hides the div and shows an input on right click + cy.get('.rightclick-action-div').rightclick().should('not.be.visible') + cy.get('.rightclick-action-input-hidden').should('be.visible') + }) + + it('.check() - check a checkbox or radio element', () => { + // https://on.cypress.io/check + + // By default, .check() will check all + // matching checkbox or radio elements in succession, one after another + cy.get('.action-checkboxes [type="checkbox"]') + .not('[disabled]') + .check() + .should('be.checked') + + cy.get('.action-radios [type="radio"]') + .not('[disabled]') + .check() + .should('be.checked') + + // .check() accepts a value argument + cy.get('.action-radios [type="radio"]').check('radio1').should('be.checked') + + // .check() accepts an array of values + cy.get('.action-multiple-checkboxes [type="checkbox"]') + .check(['checkbox1', 'checkbox2']) + .should('be.checked') + + // Ignore error checking prior to checking + cy.get('.action-checkboxes [disabled]') + .check({ force: true }) + .should('be.checked') + + cy.get('.action-radios [type="radio"]') + .check('radio3', { force: true }) + .should('be.checked') + }) + + it('.uncheck() - uncheck a checkbox element', () => { + // https://on.cypress.io/uncheck + + // By default, .uncheck() will uncheck all matching + // checkbox elements in succession, one after another + cy.get('.action-check [type="checkbox"]') + .not('[disabled]') + .uncheck() + .should('not.be.checked') + + // .uncheck() accepts a value argument + cy.get('.action-check [type="checkbox"]') + .check('checkbox1') + .uncheck('checkbox1') + .should('not.be.checked') + + // .uncheck() accepts an array of values + cy.get('.action-check [type="checkbox"]') + .check(['checkbox1', 'checkbox3']) + .uncheck(['checkbox1', 'checkbox3']) + .should('not.be.checked') + + // Ignore error checking prior to unchecking + cy.get('.action-check [disabled]') + .uncheck({ force: true }) + .should('not.be.checked') + }) + + it('.select() - select an option in a element', () => { + // https://on.cypress.io/select + + // at first, no option should be selected + cy.get('.action-select').should('have.value', '--Select a fruit--') + + // Select option(s) with matching text content + cy.get('.action-select').select('apples') + // confirm the apples were selected + // note that each value starts with "fr-" in our HTML + cy.get('.action-select').should('have.value', 'fr-apples') + + cy.get('.action-select-multiple') + .select(['apples', 'oranges', 'bananas']) + // when getting multiple values, invoke "val" method first + .invoke('val') + .should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas']) + + // Select option(s) with matching value + cy.get('.action-select') + .select('fr-bananas') + // can attach an assertion right away to the element + .should('have.value', 'fr-bananas') + + cy.get('.action-select-multiple') + .select(['fr-apples', 'fr-oranges', 'fr-bananas']) + .invoke('val') + .should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas']) + + // assert the selected values include oranges + cy.get('.action-select-multiple') + .invoke('val') + .should('include', 'fr-oranges') + }) + + it('.scrollIntoView() - scroll an element into view', () => { + // https://on.cypress.io/scrollintoview + + // normally all of these buttons are hidden, + // because they're not within + // the viewable area of their parent + // (we need to scroll to see them) + cy.get('#scroll-horizontal button').should('not.be.visible') + + // scroll the button into view, as if the user had scrolled + cy.get('#scroll-horizontal button').scrollIntoView().should('be.visible') + + cy.get('#scroll-vertical button').should('not.be.visible') + + // Cypress handles the scroll direction needed + cy.get('#scroll-vertical button').scrollIntoView().should('be.visible') + + cy.get('#scroll-both button').should('not.be.visible') + + // Cypress knows to scroll to the right and down + cy.get('#scroll-both button').scrollIntoView().should('be.visible') + }) + + it('.trigger() - trigger an event on a DOM element', () => { + // https://on.cypress.io/trigger + + // To interact with a range input (slider) + // we need to set its value & trigger the + // event to signal it changed + + // Here, we invoke jQuery's val() method to set + // the value and trigger the 'change' event + cy.get('.trigger-input-range') + .invoke('val', 25) + .trigger('change') + .get('input[type=range]') + .siblings('p') + .should('have.text', '25') + }) + + it('cy.scrollTo() - scroll the window or element to a position', () => { + // https://on.cypress.io/scrollto + + // You can scroll to 9 specific positions of an element: + // ----------------------------------- + // | topLeft top topRight | + // | | + // | | + // | | + // | left center right | + // | | + // | | + // | | + // | bottomLeft bottom bottomRight | + // ----------------------------------- + + // if you chain .scrollTo() off of cy, we will + // scroll the entire window + cy.scrollTo('bottom') + + cy.get('#scrollable-horizontal').scrollTo('right') + + // or you can scroll to a specific coordinate: + // (x axis, y axis) in pixels + cy.get('#scrollable-vertical').scrollTo(250, 250) + + // or you can scroll to a specific percentage + // of the (width, height) of the element + cy.get('#scrollable-both').scrollTo('75%', '25%') + + // control the easing of the scroll (default is 'swing') + cy.get('#scrollable-vertical').scrollTo('center', { easing: 'linear' }) + + // control the duration of the scroll (in ms) + cy.get('#scrollable-both').scrollTo('center', { duration: 2000 }) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/aliasing.spec.js b/packages/playground/ssr/cypress/examples/aliasing.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..51740b7d73affba6c364495a599380eb62fa8917 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/aliasing.spec.js @@ -0,0 +1,43 @@ +/// + +context('Aliasing', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/aliasing') + }) + + it('.as() - alias a DOM element for later use', () => { + // https://on.cypress.io/as + + // Alias a DOM element for use later + // We don't have to traverse to the element + // later in our code, we reference it with @ + + cy.get('.as-table') + .find('tbody>tr') + .first() + .find('td') + .first() + .find('button') + .as('firstBtn') + + // when we reference the alias, we place an + // @ in front of its name + cy.get('@firstBtn').click() + + cy.get('@firstBtn') + .should('have.class', 'btn-success') + .and('contain', 'Changed') + }) + + it('.as() - alias a route for later use', () => { + // Alias the route to wait for its response + cy.intercept('GET', '**/comments/*').as('getComment') + + // we have code that gets a comment when + // the button is clicked in scripts.js + cy.get('.network-btn').click() + + // https://on.cypress.io/wait + cy.wait('@getComment').its('response.statusCode').should('eq', 200) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/assertions.spec.js b/packages/playground/ssr/cypress/examples/assertions.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..be21bcec7d0782011187bbaf66ba54a295e682b8 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/assertions.spec.js @@ -0,0 +1,176 @@ +/// + +context('Assertions', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/assertions') + }) + + describe('Implicit Assertions', () => { + it('.should() - make an assertion about the current subject', () => { + // https://on.cypress.io/should + cy.get('.assertion-table') + .find('tbody tr:last') + .should('have.class', 'success') + .find('td') + .first() + // checking the text of the element in various ways + .should('have.text', 'Column content') + .should('contain', 'Column content') + .should('have.html', 'Column content') + // chai-jquery uses "is()" to check if element matches selector + .should('match', 'td') + // to match text content against a regular expression + // first need to invoke jQuery method text() + // and then match using regular expression + .invoke('text') + .should('match', /column content/i) + + // a better way to check element's text content against a regular expression + // is to use "cy.contains" + // https://on.cypress.io/contains + cy.get('.assertion-table') + .find('tbody tr:last') + // finds first element with text content matching regular expression + .contains('td', /column content/i) + .should('be.visible') + + // for more information about asserting element's text + // see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-element’s-text-contents + }) + + it('.and() - chain multiple assertions together', () => { + // https://on.cypress.io/and + cy.get('.assertions-link') + .should('have.class', 'active') + .and('have.attr', 'href') + .and('include', 'cypress.io') + }) + }) + + describe('Explicit Assertions', () => { + // https://on.cypress.io/assertions + it('expect - make an assertion about a specified subject', () => { + // We can use Chai's BDD style assertions + expect(true).to.be.true + const o = { foo: 'bar' } + + expect(o).to.equal(o) + expect(o).to.deep.equal({ foo: 'bar' }) + // matching text using regular expression + expect('FooBar').to.match(/bar$/i) + }) + + it('pass your own callback function to should()', () => { + // Pass a function to should that can have any number + // of explicit assertions within it. + // The ".should(cb)" function will be retried + // automatically until it passes all your explicit assertions or times out. + cy.get('.assertions-p') + .find('p') + .should(($p) => { + // https://on.cypress.io/$ + // return an array of texts from all of the p's + // @ts-ignore TS6133 unused variable + const texts = $p.map((i, el) => Cypress.$(el).text()) + + // jquery map returns jquery object + // and .get() convert this to simple array + const paragraphs = texts.get() + + // array should have length of 3 + expect(paragraphs, 'has 3 paragraphs').to.have.length(3) + + // use second argument to expect(...) to provide clear + // message with each assertion + expect(paragraphs, 'has expected text in each paragraph').to.deep.eq([ + 'Some text from first p', + 'More text from second p', + 'And even more text from third p', + ]) + }) + }) + + it('finds element by class name regex', () => { + cy.get('.docs-header') + .find('div') + // .should(cb) callback function will be retried + .should(($div) => { + expect($div).to.have.length(1) + + const className = $div[0].className + + expect(className).to.match(/heading-/) + }) + // .then(cb) callback is not retried, + // it either passes or fails + .then(($div) => { + expect($div, 'text content').to.have.text('Introduction') + }) + }) + + it('can throw any error', () => { + cy.get('.docs-header') + .find('div') + .should(($div) => { + if ($div.length !== 1) { + // you can throw your own errors + throw new Error('Did not find 1 element') + } + + const className = $div[0].className + + if (!className.match(/heading-/)) { + throw new Error(`Could not find class "heading-" in ${className}`) + } + }) + }) + + it('matches unknown text between two elements', () => { + /** + * Text from the first element. + * @type {string} + */ + let text + + /** + * Normalizes passed text, + * useful before comparing text with spaces and different capitalization. + * @param {string} s Text to normalize + */ + const normalizeText = (s) => s.replace(/\s/g, '').toLowerCase() + + cy.get('.two-elements') + .find('.first') + .then(($first) => { + // save text from the first element + text = normalizeText($first.text()) + }) + + cy.get('.two-elements') + .find('.second') + .should(($div) => { + // we can massage text before comparing + const secondText = normalizeText($div.text()) + + expect(secondText, 'second text').to.equal(text) + }) + }) + + it('assert - assert shape of an object', () => { + const person = { + name: 'Joe', + age: 20, + } + + assert.isObject(person, 'value is object') + }) + + it('retries the should callback until assertions pass', () => { + cy.get('#random-number').should(($div) => { + const n = parseFloat($div.text()) + + expect(n).to.be.gte(1).and.be.lte(10) + }) + }) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/connectors.spec.js b/packages/playground/ssr/cypress/examples/connectors.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..019112befb1a626564790c921d8e9263e915cfce --- /dev/null +++ b/packages/playground/ssr/cypress/examples/connectors.spec.js @@ -0,0 +1,96 @@ +/// + +context('Connectors', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/connectors') + }) + + it('.each() - iterate over an array of elements', () => { + // https://on.cypress.io/each + cy.get('.connectors-each-ul>li').each(($el, index, $list) => { + console.log($el, index, $list) + }) + }) + + it('.its() - get properties on the current subject', () => { + // https://on.cypress.io/its + cy.get('.connectors-its-ul>li') + // calls the 'length' property yielding that value + .its('length') + .should('be.gt', 2) + }) + + it('.invoke() - invoke a function on the current subject', () => { + // our div is hidden in our script.js + // $('.connectors-div').hide() + + // https://on.cypress.io/invoke + cy.get('.connectors-div') + .should('be.hidden') + // call the jquery method 'show' on the 'div.container' + .invoke('show') + .should('be.visible') + }) + + it('.spread() - spread an array as individual args to callback function', () => { + // https://on.cypress.io/spread + const arr = ['foo', 'bar', 'baz'] + + cy.wrap(arr).spread((foo, bar, baz) => { + expect(foo).to.eq('foo') + expect(bar).to.eq('bar') + expect(baz).to.eq('baz') + }) + }) + + describe('.then()', () => { + it('invokes a callback function with the current subject', () => { + // https://on.cypress.io/then + cy.get('.connectors-list > li').then(($lis) => { + expect($lis, '3 items').to.have.length(3) + expect($lis.eq(0), 'first item').to.contain('Walk the dog') + expect($lis.eq(1), 'second item').to.contain('Feed the cat') + expect($lis.eq(2), 'third item').to.contain('Write JavaScript') + }) + }) + + it('yields the returned value to the next command', () => { + cy.wrap(1) + .then((num) => { + expect(num).to.equal(1) + + return 2 + }) + .then((num) => { + expect(num).to.equal(2) + }) + }) + + it('yields the original subject without return', () => { + cy.wrap(1) + .then((num) => { + expect(num).to.equal(1) + // note that nothing is returned from this callback + }) + .then((num) => { + // this callback receives the original unchanged value 1 + expect(num).to.equal(1) + }) + }) + + it('yields the value yielded by the last Cypress command inside', () => { + cy.wrap(1) + .then((num) => { + expect(num).to.equal(1) + // note how we run a Cypress command + // the result yielded by this Cypress command + // will be passed to the second ".then" + cy.wrap(2) + }) + .then((num) => { + // this callback receives the value yielded by "cy.wrap(2)" + expect(num).to.equal(2) + }) + }) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/cookies.spec.js b/packages/playground/ssr/cypress/examples/cookies.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..247f2b013ef9c00668fbfcfd6c39632d63aabc4a --- /dev/null +++ b/packages/playground/ssr/cypress/examples/cookies.spec.js @@ -0,0 +1,79 @@ +/// + +context('Cookies', () => { + beforeEach(() => { + Cypress.Cookies.debug(true) + + cy.visit('https://example.cypress.io/commands/cookies') + + // clear cookies again after visiting to remove + // any 3rd party cookies picked up such as cloudflare + cy.clearCookies() + }) + + it('cy.getCookie() - get a browser cookie', () => { + // https://on.cypress.io/getcookie + cy.get('#getCookie .set-a-cookie').click() + + // cy.getCookie() yields a cookie object + cy.getCookie('token').should('have.property', 'value', '123ABC') + }) + + it('cy.getCookies() - get browser cookies', () => { + // https://on.cypress.io/getcookies + cy.getCookies().should('be.empty') + + cy.get('#getCookies .set-a-cookie').click() + + // cy.getCookies() yields an array of cookies + cy.getCookies() + .should('have.length', 1) + .should((cookies) => { + // each cookie has these properties + expect(cookies[0]).to.have.property('name', 'token') + expect(cookies[0]).to.have.property('value', '123ABC') + expect(cookies[0]).to.have.property('httpOnly', false) + expect(cookies[0]).to.have.property('secure', false) + expect(cookies[0]).to.have.property('domain') + expect(cookies[0]).to.have.property('path') + }) + }) + + it('cy.setCookie() - set a browser cookie', () => { + // https://on.cypress.io/setcookie + cy.getCookies().should('be.empty') + + cy.setCookie('foo', 'bar') + + // cy.getCookie() yields a cookie object + cy.getCookie('foo').should('have.property', 'value', 'bar') + }) + + it('cy.clearCookie() - clear a browser cookie', () => { + // https://on.cypress.io/clearcookie + cy.getCookie('token').should('be.null') + + cy.get('#clearCookie .set-a-cookie').click() + + cy.getCookie('token').should('have.property', 'value', '123ABC') + + // cy.clearCookies() yields null + cy.clearCookie('token').should('be.null') + + cy.getCookie('token').should('be.null') + }) + + it('cy.clearCookies() - clear browser cookies', () => { + // https://on.cypress.io/clearcookies + cy.getCookies().should('be.empty') + + cy.get('#clearCookies .set-a-cookie').click() + + cy.getCookies().should('have.length', 1) + + // cy.clearCookies() yields null + cy.clearCookies() + + cy.getCookies().should('be.empty') + }) +}) diff --git a/packages/playground/ssr/cypress/examples/cypress_api.spec.js b/packages/playground/ssr/cypress/examples/cypress_api.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..3990470a1d84c715d4cef122b38834a5e723726a --- /dev/null +++ b/packages/playground/ssr/cypress/examples/cypress_api.spec.js @@ -0,0 +1,215 @@ +/// + +context('Cypress.Commands', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/cypress-api') + }) + + // https://on.cypress.io/custom-commands + + it('.add() - create a custom command', () => { + Cypress.Commands.add( + 'console', + { + prevSubject: true, + }, + (subject, method) => { + // the previous subject is automatically received + // and the commands arguments are shifted + + // allow us to change the console method used + method = method || 'log' + + // log the subject to the console + // @ts-ignore TS7017 + console[method]('The subject is', subject) + + // whatever we return becomes the new subject + // we don't want to change the subject so + // we return whatever was passed in + return subject + } + ) + + // @ts-ignore TS2339 + cy.get('button') + .console('info') + .then(($button) => { + // subject is still $button + }) + }) +}) + +context('Cypress.Cookies', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/cypress-api') + }) + + // https://on.cypress.io/cookies + it('.debug() - enable or disable debugging', () => { + Cypress.Cookies.debug(true) + + // Cypress will now log in the console when + // cookies are set or cleared + cy.setCookie('fakeCookie', '123ABC') + cy.clearCookie('fakeCookie') + cy.setCookie('fakeCookie', '123ABC') + cy.clearCookie('fakeCookie') + cy.setCookie('fakeCookie', '123ABC') + }) + + it('.preserveOnce() - preserve cookies by key', () => { + // normally cookies are reset after each test + cy.getCookie('fakeCookie').should('not.be.ok') + + // preserving a cookie will not clear it when + // the next test starts + cy.setCookie('lastCookie', '789XYZ') + Cypress.Cookies.preserveOnce('lastCookie') + }) + + it('.defaults() - set defaults for all cookies', () => { + // now any cookie with the name 'session_id' will + // not be cleared before each new test runs + Cypress.Cookies.defaults({ + preserve: 'session_id', + }) + }) +}) + +context('Cypress.arch', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/cypress-api') + }) + + it('Get CPU architecture name of underlying OS', () => { + // https://on.cypress.io/arch + expect(Cypress.arch).to.exist + }) +}) + +context('Cypress.config()', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/cypress-api') + }) + + it('Get and set configuration options', () => { + // https://on.cypress.io/config + let myConfig = Cypress.config() + + expect(myConfig).to.have.property('animationDistanceThreshold', 5) + expect(myConfig).to.have.property('baseUrl', null) + expect(myConfig).to.have.property('defaultCommandTimeout', 4000) + expect(myConfig).to.have.property('requestTimeout', 5000) + expect(myConfig).to.have.property('responseTimeout', 30000) + expect(myConfig).to.have.property('viewportHeight', 660) + expect(myConfig).to.have.property('viewportWidth', 1000) + expect(myConfig).to.have.property('pageLoadTimeout', 60000) + expect(myConfig).to.have.property('waitForAnimations', true) + + expect(Cypress.config('pageLoadTimeout')).to.eq(60000) + + // this will change the config for the rest of your tests! + Cypress.config('pageLoadTimeout', 20000) + + expect(Cypress.config('pageLoadTimeout')).to.eq(20000) + + Cypress.config('pageLoadTimeout', 60000) + }) +}) + +context('Cypress.dom', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/cypress-api') + }) + + // https://on.cypress.io/dom + it('.isHidden() - determine if a DOM element is hidden', () => { + let hiddenP = Cypress.$('.dom-p p.hidden').get(0) + let visibleP = Cypress.$('.dom-p p.visible').get(0) + + // our first paragraph has css class 'hidden' + expect(Cypress.dom.isHidden(hiddenP)).to.be.true + expect(Cypress.dom.isHidden(visibleP)).to.be.false + }) +}) + +context('Cypress.env()', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/cypress-api') + }) + + // We can set environment variables for highly dynamic values + + // https://on.cypress.io/environment-variables + it('Get environment variables', () => { + // https://on.cypress.io/env + // set multiple environment variables + Cypress.env({ + host: 'veronica.dev.local', + api_server: 'http://localhost:8888/v1/', + }) + + // get environment variable + expect(Cypress.env('host')).to.eq('veronica.dev.local') + + // set environment variable + Cypress.env('api_server', 'http://localhost:8888/v2/') + expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/') + + // get all environment variable + expect(Cypress.env()).to.have.property('host', 'veronica.dev.local') + expect(Cypress.env()).to.have.property( + 'api_server', + 'http://localhost:8888/v2/' + ) + }) +}) + +context('Cypress.log', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/cypress-api') + }) + + it('Control what is printed to the Command Log', () => { + // https://on.cypress.io/cypress-log + }) +}) + +context('Cypress.platform', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/cypress-api') + }) + + it('Get underlying OS name', () => { + // https://on.cypress.io/platform + expect(Cypress.platform).to.be.exist + }) +}) + +context('Cypress.version', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/cypress-api') + }) + + it('Get current version of Cypress being run', () => { + // https://on.cypress.io/version + expect(Cypress.version).to.be.exist + }) +}) + +context('Cypress.spec', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/cypress-api') + }) + + it('Get current spec information', () => { + // https://on.cypress.io/spec + // wrap the object so we can inspect it easily by clicking in the command log + cy.wrap(Cypress.spec).should('include.keys', [ + 'name', + 'relative', + 'absolute', + ]) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/files.spec.js b/packages/playground/ssr/cypress/examples/files.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..fd8ae525f3547a6f0d6e57732ad814938c24fc37 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/files.spec.js @@ -0,0 +1,94 @@ +/// + +/// JSON fixture file can be loaded directly using +// the built-in JavaScript bundler +// @ts-ignore +const requiredExample = require('../../fixtures/example') + +context('Files', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/files') + }) + + beforeEach(() => { + // load example.json fixture file and store + // in the test context object + cy.fixture('example.json').as('example') + }) + + it('cy.fixture() - load a fixture', () => { + // https://on.cypress.io/fixture + + // Instead of writing a response inline you can + // use a fixture file's content. + + // when application makes an Ajax request matching "GET **/comments/*" + // Cypress will intercept it and reply with the object in `example.json` fixture + cy.intercept('GET', '**/comments/*', { fixture: 'example.json' }).as( + 'getComment' + ) + + // we have code that gets a comment when + // the button is clicked in scripts.js + cy.get('.fixture-btn').click() + + cy.wait('@getComment') + .its('response.body') + .should('have.property', 'name') + .and('include', 'Using fixtures to represent data') + }) + + it('cy.fixture() or require - load a fixture', function () { + // we are inside the "function () { ... }" + // callback and can use test context object "this" + // "this.example" was loaded in "beforeEach" function callback + expect(this.example, 'fixture in the test context').to.deep.equal( + requiredExample + ) + + // or use "cy.wrap" and "should('deep.equal', ...)" assertion + // @ts-ignore + cy.wrap(this.example, 'fixture vs require').should( + 'deep.equal', + requiredExample + ) + }) + + it('cy.readFile() - read file contents', () => { + // https://on.cypress.io/readfile + + // You can read a file and yield its contents + // The filePath is relative to your project's root. + cy.readFile('cypress.json').then((json) => { + expect(json).to.be.an('object') + }) + }) + + it('cy.writeFile() - write to a file', () => { + // https://on.cypress.io/writefile + + // You can write to a file + + // Use a response from a request to automatically + // generate a fixture file for use later + cy.request('https://jsonplaceholder.cypress.io/users').then((response) => { + cy.writeFile('cypress/fixtures/users.json', response.body) + }) + + cy.fixture('users').should((users) => { + expect(users[0].name).to.exist + }) + + // JavaScript arrays and objects are stringified + // and formatted into text. + cy.writeFile('cypress/fixtures/profile.json', { + id: 8739, + name: 'Jane', + email: 'jane@example.com', + }) + + cy.fixture('profile').should((profile) => { + expect(profile.name).to.eq('Jane') + }) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/local_storage.spec.js b/packages/playground/ssr/cypress/examples/local_storage.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..1f3402b79c303329b57dd78d9ede1578df1ea888 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/local_storage.spec.js @@ -0,0 +1,58 @@ +/// + +context('Local Storage', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/local-storage') + }) + // Although local storage is automatically cleared + // in between tests to maintain a clean state + // sometimes we need to clear the local storage manually + + it('cy.clearLocalStorage() - clear all data in local storage', () => { + // https://on.cypress.io/clearlocalstorage + cy.get('.ls-btn') + .click() + .should(() => { + expect(localStorage.getItem('prop1')).to.eq('red') + expect(localStorage.getItem('prop2')).to.eq('blue') + expect(localStorage.getItem('prop3')).to.eq('magenta') + }) + + // clearLocalStorage() yields the localStorage object + cy.clearLocalStorage().should((ls) => { + expect(ls.getItem('prop1')).to.be.null + expect(ls.getItem('prop2')).to.be.null + expect(ls.getItem('prop3')).to.be.null + }) + + // Clear key matching string in Local Storage + cy.get('.ls-btn') + .click() + .should(() => { + expect(localStorage.getItem('prop1')).to.eq('red') + expect(localStorage.getItem('prop2')).to.eq('blue') + expect(localStorage.getItem('prop3')).to.eq('magenta') + }) + + cy.clearLocalStorage('prop1').should((ls) => { + expect(ls.getItem('prop1')).to.be.null + expect(ls.getItem('prop2')).to.eq('blue') + expect(ls.getItem('prop3')).to.eq('magenta') + }) + + // Clear keys matching regex in Local Storage + cy.get('.ls-btn') + .click() + .should(() => { + expect(localStorage.getItem('prop1')).to.eq('red') + expect(localStorage.getItem('prop2')).to.eq('blue') + expect(localStorage.getItem('prop3')).to.eq('magenta') + }) + + cy.clearLocalStorage(/prop1|2/).should((ls) => { + expect(ls.getItem('prop1')).to.be.null + expect(ls.getItem('prop2')).to.be.null + expect(ls.getItem('prop3')).to.eq('magenta') + }) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/location.spec.js b/packages/playground/ssr/cypress/examples/location.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..9204287d2401d9b639bb656ff325aeb2817df065 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/location.spec.js @@ -0,0 +1,34 @@ +/// + +context('Location', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/location') + }) + + it('cy.hash() - get the current URL hash', () => { + // https://on.cypress.io/hash + cy.hash().should('be.empty') + }) + + it('cy.location() - get window.location', () => { + // https://on.cypress.io/location + cy.location().should((location) => { + expect(location.hash).to.be.empty + expect(location.href).to.eq( + 'https://example.cypress.io/commands/location' + ) + expect(location.host).to.eq('example.cypress.io') + expect(location.hostname).to.eq('example.cypress.io') + expect(location.origin).to.eq('https://example.cypress.io') + expect(location.pathname).to.eq('/commands/location') + expect(location.port).to.eq('') + expect(location.protocol).to.eq('https:') + expect(location.search).to.be.empty + }) + }) + + it('cy.url() - get the current URL', () => { + // https://on.cypress.io/url + cy.url().should('eq', 'https://example.cypress.io/commands/location') + }) +}) diff --git a/packages/playground/ssr/cypress/examples/misc.spec.js b/packages/playground/ssr/cypress/examples/misc.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..cf08a2ea8fc39231bd48a8c3ecbfd867947b47d2 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/misc.spec.js @@ -0,0 +1,99 @@ +/// + +context('Misc', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/misc') + }) + + it('.end() - end the command chain', () => { + // https://on.cypress.io/end + + // cy.end is useful when you want to end a chain of commands + // and force Cypress to re-query from the root element + cy.get('.misc-table').within(() => { + // ends the current chain and yields null + cy.contains('Cheryl').click().end() + + // queries the entire table again + cy.contains('Charles').click() + }) + }) + + it('cy.exec() - execute a system command', () => { + // execute a system command. + // so you can take actions necessary for + // your test outside the scope of Cypress. + // https://on.cypress.io/exec + + // we can use Cypress.platform string to + // select appropriate command + // https://on.cypress/io/platform + cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`) + + // on CircleCI Windows build machines we have a failure to run bash shell + // https://github.com/cypress-io/cypress/issues/5169 + // so skip some of the tests by passing flag "--env circle=true" + const isCircleOnWindows = + Cypress.platform === 'win32' && Cypress.env('circle') + + if (isCircleOnWindows) { + cy.log('Skipping test on CircleCI') + + return + } + + // cy.exec problem on Shippable CI + // https://github.com/cypress-io/cypress/issues/6718 + const isShippable = Cypress.platform === 'linux' && Cypress.env('shippable') + + if (isShippable) { + cy.log('Skipping test on ShippableCI') + + return + } + + cy.exec('echo Jane Lane').its('stdout').should('contain', 'Jane Lane') + + if (Cypress.platform === 'win32') { + cy.exec('print cypress.json').its('stderr').should('be.empty') + } else { + cy.exec('cat cypress.json').its('stderr').should('be.empty') + + cy.exec('pwd').its('code').should('eq', 0) + } + }) + + it('cy.focused() - get the DOM element that has focus', () => { + // https://on.cypress.io/focused + cy.get('.misc-form').find('#name').click() + cy.focused().should('have.id', 'name') + + cy.get('.misc-form').find('#description').click() + cy.focused().should('have.id', 'description') + }) + + context('Cypress.Screenshot', function () { + it('cy.screenshot() - take a screenshot', () => { + // https://on.cypress.io/screenshot + cy.screenshot('my-image') + }) + + it('Cypress.Screenshot.defaults() - change default config of screenshots', function () { + Cypress.Screenshot.defaults({ + blackout: ['.foo'], + capture: 'viewport', + clip: { x: 0, y: 0, width: 200, height: 200 }, + scale: false, + disableTimersAndAnimations: true, + screenshotOnRunFailure: true, + onBeforeScreenshot() {}, + onAfterScreenshot() {}, + }) + }) + }) + + it('cy.wrap() - wrap an object', () => { + // https://on.cypress.io/wrap + cy.wrap({ foo: 'bar' }).should('have.property', 'foo').and('include', 'bar') + }) +}) diff --git a/packages/playground/ssr/cypress/examples/navigation.spec.js b/packages/playground/ssr/cypress/examples/navigation.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..a7b297a16e10e08117229db62442926179bbd819 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/navigation.spec.js @@ -0,0 +1,56 @@ +/// + +context('Navigation', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io') + cy.get('.navbar-nav').contains('Commands').click() + cy.get('.dropdown-menu').contains('Navigation').click() + }) + + it("cy.go() - go back or forward in the browser's history", () => { + // https://on.cypress.io/go + + cy.location('pathname').should('include', 'navigation') + + cy.go('back') + cy.location('pathname').should('not.include', 'navigation') + + cy.go('forward') + cy.location('pathname').should('include', 'navigation') + + // clicking back + cy.go(-1) + cy.location('pathname').should('not.include', 'navigation') + + // clicking forward + cy.go(1) + cy.location('pathname').should('include', 'navigation') + }) + + it('cy.reload() - reload the page', () => { + // https://on.cypress.io/reload + cy.reload() + + // reload the page without using the cache + cy.reload(true) + }) + + it('cy.visit() - visit a remote url', () => { + // https://on.cypress.io/visit + + // Visit any sub-domain of your current domain + + // Pass options to the visit + cy.visit('https://example.cypress.io/commands/navigation', { + timeout: 50000, // increase total time for the visit to resolve + onBeforeLoad(contentWindow) { + // contentWindow is the remote page's window object + expect(typeof contentWindow === 'object').to.be.true + }, + onLoad(contentWindow) { + // contentWindow is the remote page's window object + expect(typeof contentWindow === 'object').to.be.true + }, + }) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/network_requests.spec.js b/packages/playground/ssr/cypress/examples/network_requests.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..f00388e7461ec07fbe2e4da15260bf16041e94fe --- /dev/null +++ b/packages/playground/ssr/cypress/examples/network_requests.spec.js @@ -0,0 +1,181 @@ +/// + +context('Network Requests', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/network-requests') + }) + + // Manage HTTP requests in your app + + it('cy.request() - make an XHR request', () => { + // https://on.cypress.io/request + cy.request('https://jsonplaceholder.cypress.io/comments').should( + (response) => { + expect(response.status).to.eq(200) + // the server sometimes gets an extra comment posted from another machine + // which gets returned as 1 extra object + expect(response.body) + .to.have.property('length') + .and.be.oneOf([500, 501]) + expect(response).to.have.property('headers') + expect(response).to.have.property('duration') + } + ) + }) + + it('cy.request() - verify response using BDD syntax', () => { + cy.request('https://jsonplaceholder.cypress.io/comments').then( + (response) => { + // https://on.cypress.io/assertions + expect(response).property('status').to.equal(200) + expect(response) + .property('body') + .to.have.property('length') + .and.be.oneOf([500, 501]) + expect(response).to.include.keys('headers', 'duration') + } + ) + }) + + it('cy.request() with query parameters', () => { + // will execute request + // https://jsonplaceholder.cypress.io/comments?postId=1&id=3 + cy.request({ + url: 'https://jsonplaceholder.cypress.io/comments', + qs: { + postId: 1, + id: 3, + }, + }) + .its('body') + .should('be.an', 'array') + .and('have.length', 1) + .its('0') // yields first element of the array + .should('contain', { + postId: 1, + id: 3, + }) + }) + + it('cy.request() - pass result to the second request', () => { + // first, let's find out the userId of the first user we have + cy.request('https://jsonplaceholder.cypress.io/users?_limit=1') + .its('body') // yields the response object + .its('0') // yields the first element of the returned list + // the above two commands its('body').its('0') + // can be written as its('body.0') + // if you do not care about TypeScript checks + .then((user) => { + expect(user).property('id').to.be.a('number') + // make a new post on behalf of the user + cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', { + userId: user.id, + title: 'Cypress Test Runner', + body: 'Fast, easy and reliable testing for anything that runs in a browser.', + }) + }) + // note that the value here is the returned value of the 2nd request + // which is the new post object + .then((response) => { + expect(response).property('status').to.equal(201) // new entity created + expect(response).property('body').to.contain({ + title: 'Cypress Test Runner', + }) + + // we don't know the exact post id - only that it will be > 100 + // since JSONPlaceholder has built-in 100 posts + expect(response.body).property('id').to.be.a('number').and.to.be.gt(100) + + // we don't know the user id here - since it was in above closure + // so in this test just confirm that the property is there + expect(response.body).property('userId').to.be.a('number') + }) + }) + + it('cy.request() - save response in the shared test context', () => { + // https://on.cypress.io/variables-and-aliases + cy.request('https://jsonplaceholder.cypress.io/users?_limit=1') + .its('body') + .its('0') // yields the first element of the returned list + .as('user') // saves the object in the test context + .then(function () { + // NOTE 👀 + // By the time this callback runs the "as('user')" command + // has saved the user object in the test context. + // To access the test context we need to use + // the "function () { ... }" callback form, + // otherwise "this" points at a wrong or undefined object! + cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', { + userId: this.user.id, + title: 'Cypress Test Runner', + body: 'Fast, easy and reliable testing for anything that runs in a browser.', + }) + .its('body') + .as('post') // save the new post from the response + }) + .then(function () { + // When this callback runs, both "cy.request" API commands have finished + // and the test context has "user" and "post" objects set. + // Let's verify them. + expect(this.post, 'post has the right user id') + .property('userId') + .to.equal(this.user.id) + }) + }) + + it('cy.intercept() - route responses to matching requests', () => { + // https://on.cypress.io/intercept + + let message = 'whoa, this comment does not exist' + + // Listen to GET to comments/1 + cy.intercept('GET', '**/comments/*').as('getComment') + + // we have code that gets a comment when + // the button is clicked in scripts.js + cy.get('.network-btn').click() + + // https://on.cypress.io/wait + cy.wait('@getComment') + .its('response.statusCode') + .should('be.oneOf', [200, 304]) + + // Listen to POST to comments + cy.intercept('POST', '**/comments').as('postComment') + + // we have code that posts a comment when + // the button is clicked in scripts.js + cy.get('.network-post').click() + cy.wait('@postComment').should(({ request, response }) => { + expect(request.body).to.include('email') + expect(request.headers).to.have.property('content-type') + expect(response && response.body).to.have.property( + 'name', + 'Using POST in cy.intercept()' + ) + }) + + // Stub a response to PUT comments/ **** + cy.intercept( + { + method: 'PUT', + url: '**/comments/*', + }, + { + statusCode: 404, + body: { error: message }, + headers: { 'access-control-allow-origin': '*' }, + delayMs: 500, + } + ).as('putComment') + + // we have code that puts a comment when + // the button is clicked in scripts.js + cy.get('.network-put').click() + + cy.wait('@putComment') + + // our 404 statusCode logic in scripts.js executed + cy.get('.network-put-comment').should('contain', message) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/querying.spec.js b/packages/playground/ssr/cypress/examples/querying.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..b383d47fa0944a7e7f5683fc7258fca53ec48f97 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/querying.spec.js @@ -0,0 +1,106 @@ +/// + +context('Querying', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/querying') + }) + + // The most commonly used query is 'cy.get()', you can + // think of this like the '$' in jQuery + + it('cy.get() - query DOM elements', () => { + // https://on.cypress.io/get + + cy.get('#query-btn').should('contain', 'Button') + + cy.get('.query-btn').should('contain', 'Button') + + cy.get('#querying .well>button:first').should('contain', 'Button') + // ↲ + // Use CSS selectors just like jQuery + + cy.get('[data-test-id="test-example"]').should('have.class', 'example') + + // 'cy.get()' yields jQuery object, you can get its attribute + // by invoking `.attr()` method + cy.get('[data-test-id="test-example"]') + .invoke('attr', 'data-test-id') + .should('equal', 'test-example') + + // or you can get element's CSS property + cy.get('[data-test-id="test-example"]') + .invoke('css', 'position') + .should('equal', 'static') + + // or use assertions directly during 'cy.get()' + // https://on.cypress.io/assertions + cy.get('[data-test-id="test-example"]') + .should('have.attr', 'data-test-id', 'test-example') + .and('have.css', 'position', 'static') + }) + + it('cy.contains() - query DOM elements with matching content', () => { + // https://on.cypress.io/contains + cy.get('.query-list').contains('bananas').should('have.class', 'third') + + // we can pass a regexp to `.contains()` + cy.get('.query-list').contains(/^b\w+/).should('have.class', 'third') + + cy.get('.query-list').contains('apples').should('have.class', 'first') + + // passing a selector to contains will + // yield the selector containing the text + cy.get('#querying') + .contains('ul', 'oranges') + .should('have.class', 'query-list') + + cy.get('.query-button').contains('Save Form').should('have.class', 'btn') + }) + + it('.within() - query DOM elements within a specific element', () => { + // https://on.cypress.io/within + cy.get('.query-form').within(() => { + cy.get('input:first').should('have.attr', 'placeholder', 'Email') + cy.get('input:last').should('have.attr', 'placeholder', 'Password') + }) + }) + + it('cy.root() - query the root DOM element', () => { + // https://on.cypress.io/root + + // By default, root is the document + cy.root().should('match', 'html') + + cy.get('.query-ul').within(() => { + // In this within, the root is now the ul DOM element + cy.root().should('have.class', 'query-ul') + }) + }) + + it('best practices - selecting elements', () => { + // https://on.cypress.io/best-practices#Selecting-Elements + cy.get('[data-cy=best-practices-selecting-elements]').within(() => { + // Worst - too generic, no context + cy.get('button').click() + + // Bad. Coupled to styling. Highly subject to change. + cy.get('.btn.btn-large').click() + + // Average. Coupled to the `name` attribute which has HTML semantics. + cy.get('[name=submission]').click() + + // Better. But still coupled to styling or JS event listeners. + cy.get('#main').click() + + // Slightly better. Uses an ID but also ensures the element + // has an ARIA role attribute + cy.get('#main[role=button]').click() + + // Much better. But still coupled to text content that may change. + cy.contains('Submit').click() + + // Best. Insulated from all changes. + cy.get('[data-cy=submit]').click() + }) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/spies_stubs_clocks.spec.js b/packages/playground/ssr/cypress/examples/spies_stubs_clocks.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..1b9e08377573d3766b5c56097a455612e6e9da74 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/spies_stubs_clocks.spec.js @@ -0,0 +1,214 @@ +/// +// remove no check once Cypress.sinon is typed +// https://github.com/cypress-io/cypress/issues/6720 + +context('Spies, Stubs, and Clock', () => { + it('cy.spy() - wrap a method in a spy', () => { + // https://on.cypress.io/spy + cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') + + const obj = { + foo() {}, + } + + const spy = cy.spy(obj, 'foo').as('anyArgs') + + obj.foo() + + expect(spy).to.be.called + }) + + it('cy.spy() retries until assertions pass', () => { + cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') + + const obj = { + /** + * Prints the argument passed + * @param x {any} + */ + foo(x) { + console.log('obj.foo called with', x) + }, + } + + cy.spy(obj, 'foo').as('foo') + + setTimeout(() => { + obj.foo('first') + }, 500) + + setTimeout(() => { + obj.foo('second') + }, 2500) + + cy.get('@foo').should('have.been.calledTwice') + }) + + it('cy.stub() - create a stub and/or replace a function with stub', () => { + // https://on.cypress.io/stub + cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') + + const obj = { + /** + * prints both arguments to the console + * @param a {string} + * @param b {string} + */ + foo(a, b) { + console.log('a', a, 'b', b) + }, + } + + const stub = cy.stub(obj, 'foo').as('foo') + + obj.foo('foo', 'bar') + + expect(stub).to.be.called + }) + + it('cy.clock() - control time in the browser', () => { + // https://on.cypress.io/clock + + // create the date in UTC so its always the same + // no matter what local timezone the browser is running in + const now = new Date(Date.UTC(2017, 2, 14)).getTime() + + cy.clock(now) + cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') + cy.get('#clock-div').click().should('have.text', '1489449600') + }) + + it('cy.tick() - move time in the browser', () => { + // https://on.cypress.io/tick + + // create the date in UTC so its always the same + // no matter what local timezone the browser is running in + const now = new Date(Date.UTC(2017, 2, 14)).getTime() + + cy.clock(now) + cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') + cy.get('#tick-div').click().should('have.text', '1489449600') + + cy.tick(10000) // 10 seconds passed + cy.get('#tick-div').click().should('have.text', '1489449610') + }) + + it('cy.stub() matches depending on arguments', () => { + // see all possible matchers at + // https://sinonjs.org/releases/latest/matchers/ + const greeter = { + /** + * Greets a person + * @param {string} name + */ + greet(name) { + return `Hello, ${name}!` + }, + } + + cy.stub(greeter, 'greet') + .callThrough() // if you want non-matched calls to call the real method + .withArgs(Cypress.sinon.match.string) + .returns('Hi') + .withArgs(Cypress.sinon.match.number) + .throws(new Error('Invalid name')) + + expect(greeter.greet('World')).to.equal('Hi') + // @ts-ignore + expect(() => greeter.greet(42)).to.throw('Invalid name') + expect(greeter.greet).to.have.been.calledTwice + + // non-matched calls goes the actual method + // @ts-ignore + expect(greeter.greet()).to.equal('Hello, undefined!') + }) + + it('matches call arguments using Sinon matchers', () => { + // see all possible matchers at + // https://sinonjs.org/releases/latest/matchers/ + const calculator = { + /** + * returns the sum of two arguments + * @param a {number} + * @param b {number} + */ + add(a, b) { + return a + b + }, + } + + const spy = cy.spy(calculator, 'add').as('add') + + expect(calculator.add(2, 3)).to.equal(5) + + // if we want to assert the exact values used during the call + expect(spy).to.be.calledWith(2, 3) + + // let's confirm "add" method was called with two numbers + expect(spy).to.be.calledWith( + Cypress.sinon.match.number, + Cypress.sinon.match.number + ) + + // alternatively, provide the value to match + expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3)) + + // match any value + expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3) + + // match any value from a list + expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3) + + /** + * Returns true if the given number is event + * @param {number} x + */ + const isEven = (x) => x % 2 === 0 + + // expect the value to pass a custom predicate function + // the second argument to "sinon.match(predicate, message)" is + // shown if the predicate does not pass and assertion fails + expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, 'isEven'), 3) + + /** + * Returns a function that checks if a given number is larger than the limit + * @param {number} limit + * @returns {(x: number) => boolean} + */ + const isGreaterThan = (limit) => (x) => x > limit + + /** + * Returns a function that checks if a given number is less than the limit + * @param {number} limit + * @returns {(x: number) => boolean} + */ + const isLessThan = (limit) => (x) => x < limit + + // you can combine several matchers using "and", "or" + expect(spy).to.be.calledWith( + Cypress.sinon.match.number, + Cypress.sinon + .match(isGreaterThan(2), '> 2') + .and(Cypress.sinon.match(isLessThan(4), '< 4')) + ) + + expect(spy).to.be.calledWith( + Cypress.sinon.match.number, + Cypress.sinon + .match(isGreaterThan(200), '> 200') + .or(Cypress.sinon.match(3)) + ) + + // matchers can be used from BDD assertions + cy.get('@add').should( + 'have.been.calledWith', + Cypress.sinon.match.number, + Cypress.sinon.match(3) + ) + + // you can alias matchers for shorter test code + const { match: M } = Cypress.sinon + + cy.get('@add').should('have.been.calledWith', M.number, M(3)) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/traversal.spec.js b/packages/playground/ssr/cypress/examples/traversal.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..e1c30fb65714c76884aff977f6f2c598f05b76b9 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/traversal.spec.js @@ -0,0 +1,116 @@ +/// + +context('Traversal', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/traversal') + }) + + it('.children() - get child DOM elements', () => { + // https://on.cypress.io/children + cy.get('.traversal-breadcrumb') + .children('.active') + .should('contain', 'Data') + }) + + it('.closest() - get closest ancestor DOM element', () => { + // https://on.cypress.io/closest + cy.get('.traversal-badge').closest('ul').should('have.class', 'list-group') + }) + + it('.eq() - get a DOM element at a specific index', () => { + // https://on.cypress.io/eq + cy.get('.traversal-list>li').eq(1).should('contain', 'siamese') + }) + + it('.filter() - get DOM elements that match the selector', () => { + // https://on.cypress.io/filter + cy.get('.traversal-nav>li').filter('.active').should('contain', 'About') + }) + + it('.find() - get descendant DOM elements of the selector', () => { + // https://on.cypress.io/find + cy.get('.traversal-pagination') + .find('li') + .find('a') + .should('have.length', 7) + }) + + it('.first() - get first DOM element', () => { + // https://on.cypress.io/first + cy.get('.traversal-table td').first().should('contain', '1') + }) + + it('.last() - get last DOM element', () => { + // https://on.cypress.io/last + cy.get('.traversal-buttons .btn').last().should('contain', 'Submit') + }) + + it('.next() - get next sibling DOM element', () => { + // https://on.cypress.io/next + cy.get('.traversal-ul') + .contains('apples') + .next() + .should('contain', 'oranges') + }) + + it('.nextAll() - get all next sibling DOM elements', () => { + // https://on.cypress.io/nextall + cy.get('.traversal-next-all') + .contains('oranges') + .nextAll() + .should('have.length', 3) + }) + + it('.nextUntil() - get next sibling DOM elements until next el', () => { + // https://on.cypress.io/nextuntil + cy.get('#veggies').nextUntil('#nuts').should('have.length', 3) + }) + + it('.not() - remove DOM elements from set of DOM elements', () => { + // https://on.cypress.io/not + cy.get('.traversal-disabled .btn') + .not('[disabled]') + .should('not.contain', 'Disabled') + }) + + it('.parent() - get parent DOM element from DOM elements', () => { + // https://on.cypress.io/parent + cy.get('.traversal-mark').parent().should('contain', 'Morbi leo risus') + }) + + it('.parents() - get parent DOM elements from DOM elements', () => { + // https://on.cypress.io/parents + cy.get('.traversal-cite').parents().should('match', 'blockquote') + }) + + it('.parentsUntil() - get parent DOM elements from DOM elements until el', () => { + // https://on.cypress.io/parentsuntil + cy.get('.clothes-nav') + .find('.active') + .parentsUntil('.clothes-nav') + .should('have.length', 2) + }) + + it('.prev() - get previous sibling DOM element', () => { + // https://on.cypress.io/prev + cy.get('.birds').find('.active').prev().should('contain', 'Lorikeets') + }) + + it('.prevAll() - get all previous sibling DOM elements', () => { + // https://on.cypress.io/prevall + cy.get('.fruits-list').find('.third').prevAll().should('have.length', 2) + }) + + it('.prevUntil() - get all previous sibling DOM elements until el', () => { + // https://on.cypress.io/prevuntil + cy.get('.foods-list') + .find('#nuts') + .prevUntil('#veggies') + .should('have.length', 3) + }) + + it('.siblings() - get all sibling DOM elements', () => { + // https://on.cypress.io/siblings + cy.get('.traversal-pills .active').siblings().should('have.length', 2) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/utilities.spec.js b/packages/playground/ssr/cypress/examples/utilities.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..7ffca217ae0809037bb0409e2cec4499d4d9c165 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/utilities.spec.js @@ -0,0 +1,111 @@ +/// + +context('Utilities', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/utilities') + }) + + it('Cypress._ - call a lodash method', () => { + // https://on.cypress.io/_ + cy.request('https://jsonplaceholder.cypress.io/users').then((response) => { + let ids = Cypress._.chain(response.body).map('id').take(3).value() + + expect(ids).to.deep.eq([1, 2, 3]) + }) + }) + + it('Cypress.$ - call a jQuery method', () => { + // https://on.cypress.io/$ + let $li = Cypress.$('.utility-jquery li:first') + + cy.wrap($li) + .should('not.have.class', 'active') + .click() + .should('have.class', 'active') + }) + + it('Cypress.Blob - blob utilities and base64 string conversion', () => { + // https://on.cypress.io/blob + cy.get('.utility-blob').then(($div) => { + // https://github.com/nolanlawson/blob-util#imgSrcToDataURL + // get the dataUrl string for the javascript-logo + return Cypress.Blob.imgSrcToDataURL( + 'https://example.cypress.io/assets/img/javascript-logo.png', + undefined, + 'anonymous' + ).then((dataUrl) => { + // create an element and set its src to the dataUrl + let img = Cypress.$('', { src: dataUrl }) + + // need to explicitly return cy here since we are initially returning + // the Cypress.Blob.imgSrcToDataURL promise to our test + // append the image + $div.append(img) + + cy.get('.utility-blob img').click().should('have.attr', 'src', dataUrl) + }) + }) + }) + + it('Cypress.minimatch - test out glob patterns against strings', () => { + // https://on.cypress.io/minimatch + let matching = Cypress.minimatch('/users/1/comments', '/users/*/comments', { + matchBase: true, + }) + + expect(matching, 'matching wildcard').to.be.true + + matching = Cypress.minimatch('/users/1/comments/2', '/users/*/comments', { + matchBase: true, + }) + + expect(matching, 'comments').to.be.false + + // ** matches against all downstream path segments + matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/**', { + matchBase: true, + }) + + expect(matching, 'comments').to.be.true + + // whereas * matches only the next path segment + + matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/*', { + matchBase: false, + }) + + expect(matching, 'comments').to.be.false + }) + + it('Cypress.Promise - instantiate a bluebird promise', () => { + // https://on.cypress.io/promise + let waited = false + + /** + * @return Bluebird + */ + function waitOneSecond() { + // return a promise that resolves after 1 second + // @ts-ignore TS2351 (new Cypress.Promise) + return new Cypress.Promise((resolve, reject) => { + setTimeout(() => { + // set waited to true + waited = true + + // resolve with 'foo' string + resolve('foo') + }, 1000) + }) + } + + cy.then(() => { + // return a promise to cy.then() that + // is awaited until it resolves + // @ts-ignore TS7006 + return waitOneSecond().then((str) => { + expect(str).to.eq('foo') + expect(waited).to.be.true + }) + }) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/viewport.spec.js b/packages/playground/ssr/cypress/examples/viewport.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..dbcd7eeddd01e0c8f6b79ed2e8ebe69f67ce666d --- /dev/null +++ b/packages/playground/ssr/cypress/examples/viewport.spec.js @@ -0,0 +1,59 @@ +/// + +context('Viewport', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/viewport') + }) + + it('cy.viewport() - set the viewport size and dimension', () => { + // https://on.cypress.io/viewport + + cy.get('#navbar').should('be.visible') + cy.viewport(320, 480) + + // the navbar should have collapse since our screen is smaller + cy.get('#navbar').should('not.be.visible') + cy.get('.navbar-toggle').should('be.visible').click() + cy.get('.nav').find('a').should('be.visible') + + // lets see what our app looks like on a super large screen + cy.viewport(2999, 2999) + + // cy.viewport() accepts a set of preset sizes + // to easily set the screen to a device's width and height + + // We added a cy.wait() between each viewport change so you can see + // the change otherwise it is a little too fast to see :) + + cy.viewport('macbook-15') + cy.wait(200) + cy.viewport('macbook-13') + cy.wait(200) + cy.viewport('macbook-11') + cy.wait(200) + cy.viewport('ipad-2') + cy.wait(200) + cy.viewport('ipad-mini') + cy.wait(200) + cy.viewport('iphone-6+') + cy.wait(200) + cy.viewport('iphone-6') + cy.wait(200) + cy.viewport('iphone-5') + cy.wait(200) + cy.viewport('iphone-4') + cy.wait(200) + cy.viewport('iphone-3') + cy.wait(200) + + // cy.viewport() accepts an orientation for all presets + // the default orientation is 'portrait' + cy.viewport('ipad-2', 'portrait') + cy.wait(200) + cy.viewport('iphone-4', 'landscape') + cy.wait(200) + + // The viewport will be reset back to the default dimensions + // in between tests (the default can be set in cypress.json) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/waiting.spec.js b/packages/playground/ssr/cypress/examples/waiting.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..7e4c5e5eb4480d02b2f9cebbfc3e163b9d1f8df6 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/waiting.spec.js @@ -0,0 +1,33 @@ +/// + +context('Waiting', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/waiting') + }) + // BE CAREFUL of adding unnecessary wait times. + // https://on.cypress.io/best-practices#Unnecessary-Waiting + + // https://on.cypress.io/wait + it('cy.wait() - wait for a specific amount of time', () => { + cy.get('.wait-input1').type('Wait 1000ms after typing') + cy.wait(1000) + cy.get('.wait-input2').type('Wait 1000ms after typing') + cy.wait(1000) + cy.get('.wait-input3').type('Wait 1000ms after typing') + cy.wait(1000) + }) + + it('cy.wait() - wait for a specific route', () => { + // Listen to GET to comments/1 + cy.intercept('GET', '**/comments/*').as('getComment') + + // we have code that gets a comment when + // the button is clicked in scripts.js + cy.get('.network-btn').click() + + // wait for GET comments/1 + cy.wait('@getComment') + .its('response.statusCode') + .should('be.oneOf', [200, 304]) + }) +}) diff --git a/packages/playground/ssr/cypress/examples/window.spec.js b/packages/playground/ssr/cypress/examples/window.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..f94b64971db16f7f69b38958a77897b321ffeec8 --- /dev/null +++ b/packages/playground/ssr/cypress/examples/window.spec.js @@ -0,0 +1,22 @@ +/// + +context('Window', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/window') + }) + + it('cy.window() - get the global window object', () => { + // https://on.cypress.io/window + cy.window().should('have.property', 'top') + }) + + it('cy.document() - get the document object', () => { + // https://on.cypress.io/document + cy.document().should('have.property', 'charset').and('eq', 'UTF-8') + }) + + it('cy.title() - get the title', () => { + // https://on.cypress.io/title + cy.title().should('include', 'Kitchen Sink') + }) +}) diff --git a/packages/playground/ssr/cypress/integration/ssr.spec.js b/packages/playground/ssr/cypress/integration/ssr.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..11d1b90459cc547cc84b68e86f58beeed1241ab6 --- /dev/null +++ b/packages/playground/ssr/cypress/integration/ssr.spec.js @@ -0,0 +1,15 @@ +/// + +context('Waiting', () => { + beforeEach(() => { + cy.visit('http://localhost:3000') + }) + + it('ssr-view', () => { + cy.get('.ssr-view').should('have.text', 'ssr-view') + }) + it('ssr-mismatch', () => { + cy.wait(1000) + cy.get('#ssr-log').should('not.contain.text', 'Hydration') + }) +}) diff --git a/packages/playground/ssr/cypress/plugins/index.js b/packages/playground/ssr/cypress/plugins/index.js new file mode 100644 index 0000000000000000000000000000000000000000..59b2bab6e4e6059f0bd4f1ff986f8a9a2f381af9 --- /dev/null +++ b/packages/playground/ssr/cypress/plugins/index.js @@ -0,0 +1,22 @@ +/// +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +/** + * @type {Cypress.PluginConfig} + */ +// eslint-disable-next-line no-unused-vars +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +} diff --git a/packages/playground/ssr/cypress/support/commands.js b/packages/playground/ssr/cypress/support/commands.js new file mode 100644 index 0000000000000000000000000000000000000000..119ab03f7cda1f826e896639924a8612d075d3fc --- /dev/null +++ b/packages/playground/ssr/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) diff --git a/packages/playground/ssr/cypress/support/index.js b/packages/playground/ssr/cypress/support/index.js new file mode 100644 index 0000000000000000000000000000000000000000..d68db96df2697e0835f5c490db0c2cc81673f407 --- /dev/null +++ b/packages/playground/ssr/cypress/support/index.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/packages/playground/ssr/src/components/ssr-progress/ssr-progress.vue b/packages/playground/ssr/src/components/ssr-progress/ssr-progress.vue new file mode 100644 index 0000000000000000000000000000000000000000..4f566f27d68c9804816aa2d89123b29b53dc9049 --- /dev/null +++ b/packages/playground/ssr/src/components/ssr-progress/ssr-progress.vue @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/playground/ssr/src/components/ssr-text/ssr-text.vue b/packages/playground/ssr/src/components/ssr-text/ssr-text.vue new file mode 100644 index 0000000000000000000000000000000000000000..d40e711b83e85339068b6f80a5ffcab43d837fa9 --- /dev/null +++ b/packages/playground/ssr/src/components/ssr-text/ssr-text.vue @@ -0,0 +1,11 @@ + + {{ msg }}text + ssr-text + + \ No newline at end of file diff --git a/packages/playground/ssr/src/components/ssr-view/ssr-view.vue b/packages/playground/ssr/src/components/ssr-view/ssr-view.vue new file mode 100644 index 0000000000000000000000000000000000000000..14ba4f01bd4ddba33833be22eebdbf690cd2cfcc --- /dev/null +++ b/packages/playground/ssr/src/components/ssr-view/ssr-view.vue @@ -0,0 +1,3 @@ + + ssr-view + \ No newline at end of file diff --git a/packages/playground/ssr/src/main.js b/packages/playground/ssr/src/main.js index 34dd3d39024ce10b863a33d0adbfe567577795c4..12caadbd3858cc8d87e4671029a55198f0c7c50f 100644 --- a/packages/playground/ssr/src/main.js +++ b/packages/playground/ssr/src/main.js @@ -2,6 +2,11 @@ import { createSSRApp } from 'vue' import App from './App.vue' export function createApp() { const app = createSSRApp(App) + // `trace` 是组件的继承关系追踪 + app.config.warnHandler = function (msg) { + const ssrLogElem = document.getElementById('ssr-log') + ssrLogElem.innerHTML = ssrLogElem.innerHTML + '\n' + msg + } return { app, } diff --git a/packages/playground/ssr/src/pages/index/index.vue b/packages/playground/ssr/src/pages/index/index.vue index 23afcff30b6809e6ee54ea8e758206e91457702a..94ff3b0c97178e12ab578c0878c9627e63eb2dbd 100644 --- a/packages/playground/ssr/src/pages/index/index.vue +++ b/packages/playground/ssr/src/pages/index/index.vue @@ -1,10 +1,8 @@ - - - - {{ title }} - - + + + + diff --git a/packages/uni-components/src/components/progress/index.tsx b/packages/uni-components/src/components/progress/index.tsx index 7365c30821cd05117102fe44b7b1c1cac98e7d7d..830455859268b02d22a0755f48937ee56fa514c6 100644 --- a/packages/uni-components/src/components/progress/index.tsx +++ b/packages/uni-components/src/components/progress/index.tsx @@ -90,7 +90,7 @@ export default /*#__PURE__*/ defineComponent({ - {showInfo ? ( // {currentPercent}% 的写法会影响 SSR Hydration + {showInfo ? ( // {currentPercent}% 的写法会影响 SSR Hydration (tsx插件的问题) {currentPercent + '%'} ) : ( '' diff --git a/packages/uni-h5/vite.config.ts b/packages/uni-h5/vite.config.ts index 6ecea456a2674cab9d5e3229a35f4a9a5ee6b722..60b5edcee943962d05abb9e20bd452efb190a5ec 100644 --- a/packages/uni-h5/vite.config.ts +++ b/packages/uni-h5/vite.config.ts @@ -7,7 +7,7 @@ import strip from '@rollup/plugin-strip' import replace from '@rollup/plugin-replace' import { stripOptions } from '@dcloudio/uni-cli-shared' -import { isCustomElement } from '../uni-shared' +import { isCustomElement } from '@dcloudio/uni-shared' const moduleAlias = require('module-alias') moduleAlias.addAlias( diff --git a/yarn.lock b/yarn.lock index 182d2634051322d127b9c426f1b705b211e416fd..b759a34181ec71992de12702c10a00cf1f2dd61e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1808,9 +1808,9 @@ camelcase@^6.0.0: integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== caniuse-lite@^1.0.30001196, caniuse-lite@^1.0.30001219: - version "1.0.30001225" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001225.tgz#4a79952082ddbc2b0e707f05696372b07748eeea" - integrity sha512-oDbH3OluyOsP4NEBA8y3XUNZMaN5yp/Ra+WGs9IebgaclHSks70axdwFH82FBt/HmrwShBmltHkU3ti9mgxQgA== + version "1.0.30001227" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001227.tgz#437fe8b514ac4512d9bdb4ea79f20c400bd0b4d1" + integrity sha512-HHZT4QpUw4Pf45IE3xxKAPgAN3q2aRai/x5TSHP8lrrKzARoH0IeBviwStcRi5lsFlscTkBbXC7gXyms43151A== capture-exit@^2.0.0: version "2.0.0"
{currentPercent + '%'}