test: e2e test for composition API examples

This commit is contained in:
Evan You 2022-05-30 17:20:43 +08:00
parent 460856510d
commit e5a6fe5da7
17 changed files with 923 additions and 73 deletions

View File

@ -24,7 +24,7 @@
},
computed: {
compiledMarkdown: function () {
return marked(this.input, { sanitize: true })
return marked.marked(this.input)
}
},
methods: {

View File

@ -1,5 +1,5 @@
// The raw data to observe
var stats = [
var globalStats = [
{ label: 'A', value: 100 },
{ label: 'B', value: 100 },
{ label: 'C', value: 100 },
@ -16,10 +16,12 @@ Vue.component('polygraph', {
// a computed property for the polygon's points
points: function () {
var total = this.stats.length
return this.stats.map(function (stat, i) {
return this.stats
.map(function (stat, i) {
var point = valueToPoint(stat.value, i, total)
return point.x + ',' + point.y
}).join(' ')
})
.join(' ')
}
},
components: {
@ -33,11 +35,7 @@ Vue.component('polygraph', {
template: '#axis-label-template',
computed: {
point: function () {
return valueToPoint(
+this.stat.value + 10,
this.index,
this.total
)
return valueToPoint(+this.stat.value + 10, this.index, this.total)
}
}
}
@ -48,7 +46,7 @@ Vue.component('polygraph', {
function valueToPoint(value, index, total) {
var x = 0
var y = -value * 0.8
var angle = Math.PI * 2 / total * index
var angle = ((Math.PI * 2) / total) * index
var cos = Math.cos(angle)
var sin = Math.sin(angle)
var tx = x * cos - y * sin + 100
@ -64,7 +62,7 @@ new Vue({
el: '#demo',
data: {
newLabel: '',
stats: stats
stats: globalStats
},
methods: {
add: function (e) {
@ -80,7 +78,7 @@ new Vue({
if (this.stats.length > 3) {
this.stats.splice(this.stats.indexOf(stat), 1)
} else {
alert('Can\'t delete more!')
alert("Can't delete more!")
}
}
}

View File

@ -0,0 +1,75 @@
<script src="../../dist/vue.min.js"></script>
<div id="demo">
<h1>Latest Vue.js Commits</h1>
<template v-for="branch in branches">
<input type="radio"
:id="branch"
:value="branch"
name="branch"
v-model="currentBranch">
<label :for="branch">{{ branch }}</label>
</template>
<p>vuejs/vue@{{ currentBranch }}</p>
<ul>
<li v-for="{ html_url, sha, author, commit } in commits">
<a :href="html_url" target="_blank" class="commit">{{ sha.slice(0, 7) }}</a>
- <span class="message">{{ truncate(commit.message) }}</span><br>
by <span class="author"><a :href="author.html_url" target="_blank">{{ commit.author.name }}</a></span>
at <span class="date">{{ formatDate(commit.author.date) }}</span>
</li>
</ul>
</div>
<script>
const { ref, watchEffect } = Vue
const API_URL = `https://api.github.com/repos/vuejs/vue/commits?per_page=3&sha=`
const truncate = v => {
const newline = v.indexOf('\n')
return newline > 0 ? v.slice(0, newline) : v
}
const formatDate = v => v.replace(/T|Z/g, ' ')
new Vue({
setup() {
const currentBranch = ref('main')
const commits = ref(null)
watchEffect(() => {
fetch(`${API_URL}${currentBranch.value}`)
.then(res => res.json())
.then(data => {
console.log(data)
commits.value = data
})
})
return {
branches: ['main', 'dev'],
currentBranch,
commits,
truncate,
formatDate
}
}
}).$mount('#demo')
</script>
<style>
#demo {
font-family: 'Helvetica', Arial, sans-serif;
}
a {
text-decoration: none;
color: #f66;
}
li {
line-height: 1.5em;
margin-bottom: 20px;
}
.author, .date {
font-weight: bold;
}
</style>

View File

@ -0,0 +1,173 @@
<script src="../../dist/vue.min.js"></script>
<!-- DemoGrid component template -->
<script type="text/x-template" id="grid-template">
<table v-if="filteredData.length">
<thead>
<tr>
<th v-for="key in columns"
@click="sortBy(key)"
:class="{ active: state.sortKey == key }">
{{ capitalize(key) }}
<span class="arrow" :class="state.sortOrders[key] > 0 ? 'asc' : 'dsc'">
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="entry in filteredData">
<td v-for="key in columns">
{{entry[key]}}
</td>
</tr>
</tbody>
</table>
<p v-else>No matches found.</p>
</script>
<!-- DemoGrid component script -->
<script>
const { reactive, computed } = Vue
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)
const DemoGrid = {
template: '#grid-template',
props: {
data: Array,
columns: Array,
filterKey: String
},
setup(props) {
const state = reactive({
sortKey: '',
sortOrders: props.columns.reduce((o, key) => (o[key] = 1, o), {})
})
const filteredData = computed(() => {
let { data, filterKey } = props
if (filterKey) {
filterKey = filterKey.toLowerCase()
data = data.filter(row => {
return Object.keys(row).some(key => {
return String(row[key]).toLowerCase().indexOf(filterKey) > -1
})
})
}
const { sortKey } = state
if (sortKey) {
const order = state.sortOrders[sortKey]
data = data.slice().sort((a, b) => {
a = a[sortKey]
b = b[sortKey]
return (a === b ? 0 : a > b ? 1 : -1) * order
})
}
return data
})
function sortBy(key) {
state.sortKey = key
state.sortOrders[key] *= -1
}
return {
state,
filteredData,
sortBy,
capitalize
}
}
}
</script>
<!-- App template (in DOM) -->
<div id="demo">
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<demo-grid
:data="gridData"
:columns="gridColumns"
:filter-key="searchQuery">
</demo-grid>
</div>
<!-- App script -->
<script>
new Vue({
components: {
DemoGrid
},
data: () => ({
searchQuery: '',
gridColumns: ['name', 'power'],
gridData: [
{ name: 'Chuck Norris', power: Infinity },
{ name: 'Bruce Lee', power: 9000 },
{ name: 'Jackie Chan', power: 7000 },
{ name: 'Jet Li', power: 8000 }
]
})
}).$mount('#demo')
</script>
<style>
body {
font-family: Helvetica Neue, Arial, sans-serif;
font-size: 14px;
color: #444;
}
table {
border: 2px solid #42b983;
border-radius: 3px;
background-color: #fff;
}
th {
background-color: #42b983;
color: rgba(255,255,255,0.66);
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
td {
background-color: #f9f9f9;
}
th, td {
min-width: 120px;
padding: 10px 20px;
}
th.active {
color: #fff;
}
th.active .arrow {
opacity: 1;
}
.arrow {
display: inline-block;
vertical-align: middle;
width: 0;
height: 0;
margin-left: 5px;
opacity: 0.66;
}
.arrow.asc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid #fff;
}
.arrow.dsc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #fff;
}
</style>

View File

@ -0,0 +1,66 @@
<script src="../../node_modules/marked/marked.min.js"></script>
<script src="../../node_modules/lodash/lodash.min.js"></script>
<script src="../../dist/vue.min.js"></script>
<div id="editor">
<textarea :value="input" @input="update"></textarea>
<div v-html="output"></div>
</div>
<script>
const { ref, computed } = Vue
new Vue({
setup() {
const input = ref('# hello')
const output = computed(() => marked.marked(input.value))
const update = _.debounce(e => {
input.value = e.target.value
}, 300)
return {
input,
output,
update
}
}
}).$mount('#editor')
</script>
<style>
html,
body,
#editor {
margin: 0;
height: 100%;
font-family: 'Helvetica Neue', Arial, sans-serif;
color: #333;
}
textarea,
#editor div {
display: inline-block;
width: 49%;
height: 100%;
vertical-align: top;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 0 20px;
}
textarea {
border: none;
border-right: 1px solid #ccc;
resize: none;
outline: none;
background-color: #f6f6f6;
font-size: 14px;
font-family: 'Monaco', courier, monospace;
padding: 20px;
}
code {
color: #f66;
}
</style>

View File

@ -0,0 +1,172 @@
<script src="../../dist/vue.min.js"></script>
<script>
const { ref, computed, createApp } = Vue
// math helper...
function valueToPoint(value, index, total) {
var x = 0
var y = -value * 0.8
var angle = ((Math.PI * 2) / total) * index
var cos = Math.cos(angle)
var sin = Math.sin(angle)
var tx = x * cos - y * sin + 100
var ty = x * sin + y * cos + 100
return {
x: tx,
y: ty
}
}
const AxisLabel = {
template: '<text :x="point.x" :y="point.y">{{stat.label}}</text>',
props: {
stat: Object,
index: Number,
total: Number
},
setup(props) {
return {
point: computed(() =>
valueToPoint(+props.stat.value + 10, props.index, props.total)
)
}
}
}
</script>
<!-- template for the polygraph component. -->
<script type="text/x-template" id="polygraph-template">
<g>
<polygon :points="points"></polygon>
<circle cx="100" cy="100" r="80"></circle>
<axis-label
v-for="(stat, index) in stats"
:stat="stat"
:index="index"
:total="stats.length">
</axis-label>
</g>
</script>
<script>
const Polygraph = {
props: ['stats'],
template: '#polygraph-template',
setup(props) {
return {
points: computed(() => {
const total = props.stats.length
return props.stats
.map((stat, i) => {
const point = valueToPoint(stat.value, i, total)
return point.x + ',' + point.y
})
.join(' ')
})
}
},
components: {
AxisLabel
}
}
</script>
<!-- demo root element -->
<div id="demo">
<!-- Use the polygraph component -->
<svg width="200" height="200">
<polygraph :stats="stats"></polygraph>
</svg>
<!-- controls -->
<div v-for="stat in stats">
<label>{{stat.label}}</label>
<input type="range" v-model="stat.value" min="0" max="100" />
<span>{{stat.value}}</span>
<button @click="remove(stat)" class="remove">X</button>
</div>
<form id="add">
<input name="newlabel" v-model="newLabel" />
<button @click="add">Add a Stat</button>
</form>
<pre id="raw">{{ stats }}</pre>
</div>
<script>
const globalStats = [
{ label: 'A', value: 100 },
{ label: 'B', value: 100 },
{ label: 'C', value: 100 },
{ label: 'D', value: 100 },
{ label: 'E', value: 100 },
{ label: 'F', value: 100 }
]
new Vue({
components: {
Polygraph
},
setup() {
const newLabel = ref('')
const stats = ref(globalStats)
function add(e) {
e.preventDefault()
if (!newLabel.value) return
stats.value.push({
label: newLabel.value,
value: 100
})
newLabel.value = ''
}
function remove(stat) {
if (stats.value.length > 3) {
stats.value.splice(stats.value.indexOf(stat), 1)
} else {
alert("Can't delete more!")
}
}
return {
newLabel,
stats,
add,
remove
}
}
}).$mount('#demo')
</script>
<style>
body {
font-family: Helvetica Neue, Arial, sans-serif;
}
polygon {
fill: #42b983;
opacity: 0.75;
}
circle {
fill: transparent;
stroke: #999;
}
text {
font-family: Helvetica Neue, Arial, sans-serif;
font-size: 10px;
fill: #666;
}
label {
display: inline-block;
margin-left: 10px;
width: 20px;
}
#raw {
position: absolute;
top: 0;
left: 300px;
}
</style>

View File

@ -0,0 +1,241 @@
<script src="../../dist/vue.min.js"></script>
<link
rel="stylesheet"
href="../../node_modules/todomvc-app-css/index.css"
/>
<div id="app">
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input
class="new-todo"
autofocus
autocomplete="off"
placeholder="What needs to be done?"
v-model="state.newTodo"
@keyup.enter="addTodo"
/>
</header>
<section class="main" v-show="state.todos.length">
<input
id="toggle-all"
class="toggle-all"
type="checkbox"
v-model="state.allDone"
/>
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li
v-for="todo in state.filteredTodos"
class="todo"
:key="todo.id"
:class="{ completed: todo.completed, editing: todo === state.editedTodo }"
>
<div class="view">
<input class="toggle" type="checkbox" v-model="todo.completed" />
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
<input
class="edit"
type="text"
v-model="todo.title"
v-todo-focus="todo === state.editedTodo"
@blur="doneEdit(todo)"
@keyup.enter="doneEdit(todo)"
@keyup.escape="cancelEdit(todo)"
/>
</li>
</ul>
</section>
<footer class="footer" v-show="state.todos.length">
<span class="todo-count">
<strong>{{ state.remaining }}</strong>
<span>{{ state.remainingText }}</span>
</span>
<ul class="filters">
<li>
<a href="#/all" :class="{ selected: state.visibility === 'all' }"
>All</a
>
</li>
<li>
<a
href="#/active"
:class="{ selected: state.visibility === 'active' }"
>Active</a
>
</li>
<li>
<a
href="#/completed"
:class="{ selected: state.visibility === 'completed' }"
>Completed</a
>
</li>
</ul>
<button
class="clear-completed"
@click="removeCompleted"
v-show="state.todos.length > state.remaining"
>
Clear completed
</button>
</footer>
</section>
</div>
<script>
const { reactive, computed, watchEffect, onMounted, onUnmounted } = Vue
const STORAGE_KEY = 'todos-vuejs-3.x'
const todoStorage = {
fetch() {
const todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
todos.forEach((todo, index) => {
todo.id = index
})
todoStorage.uid = todos.length
return todos
},
save(todos) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
}
}
const filters = {
all(todos) {
return todos
},
active(todos) {
return todos.filter(todo => {
return !todo.completed
})
},
completed(todos) {
return todos.filter(function (todo) {
return todo.completed
})
}
}
function pluralize(n) {
return n === 1 ? 'item' : 'items'
}
new Vue({
setup() {
const state = reactive({
todos: todoStorage.fetch(),
editedTodo: null,
newTodo: '',
beforeEditCache: '',
visibility: 'all',
remaining: computed(() => {
return filters.active(state.todos).length
}),
remainingText: computed(() => {
return ` ${pluralize(state.remaining)} left`
}),
filteredTodos: computed(() => {
return filters[state.visibility](state.todos)
}),
allDone: computed({
get: function () {
return state.remaining === 0
},
set: function (value) {
state.todos.forEach(todo => {
todo.completed = value
})
}
})
})
watchEffect(() => {
todoStorage.save(state.todos)
})
onMounted(() => {
window.addEventListener('hashchange', onHashChange)
onHashChange()
})
onUnmounted(() => {
window.removeEventListener('hashchange', onHashChange)
})
function onHashChange() {
const visibility = window.location.hash.replace(/#\/?/, '')
if (filters[visibility]) {
state.visibility = visibility
} else {
window.location.hash = ''
state.visibility = 'all'
}
}
function addTodo() {
const value = state.newTodo && state.newTodo.trim()
if (!value) {
return
}
state.todos.push({
id: todoStorage.uid++,
title: value,
completed: false
})
state.newTodo = ''
}
function removeTodo(todo) {
state.todos.splice(state.todos.indexOf(todo), 1)
}
function editTodo(todo) {
state.beforeEditCache = todo.title
state.editedTodo = todo
}
function doneEdit(todo) {
if (!state.editedTodo) {
return
}
state.editedTodo = null
todo.title = todo.title.trim()
if (!todo.title) {
removeTodo(todo)
}
}
function cancelEdit(todo) {
state.editedTodo = null
todo.title = state.beforeEditCache
}
function removeCompleted() {
state.todos = filters.active(state.todos)
}
return {
state,
addTodo,
removeTodo,
editTodo,
doneEdit,
cancelEdit,
removeCompleted
}
},
directives: {
'todo-focus': (el, { value }) => {
if (value) {
el.focus()
}
}
}
}).$mount('#app')
</script>

View File

@ -0,0 +1,124 @@
<script src="../../dist/vue.min.js"></script>
<!-- item template -->
<script type="text/x-template" id="item-template">
<li>
<div
:class="{bold: isFolder}"
@click="toggle"
@dblclick="changeType">
{{model.name}}
<span v-if="isFolder">[{{open ? '-' : '+'}}]</span>
</div>
<ul v-if="isFolder" v-show="open">
<tree-item
class="item"
v-for="model in model.children"
:model="model">
</tree-item>
<li class="add" @click="addChild">+</li>
</ul>
</li>
</script>
<!-- item script -->
<script>
const { reactive, computed, toRefs } = Vue
const TreeItem = {
name: 'TreeItem', // necessary for self-reference
template: '#item-template',
props: {
model: Object
},
setup(props) {
const state = reactive({
open: false,
isFolder: computed(() => {
return props.model.children && props.model.children.length
})
})
function toggle() {
state.open = !state.open
}
function changeType() {
if (!state.isFolder) {
Vue.set(props.model, 'children', [])
addChild()
state.open = true
}
}
function addChild() {
props.model.children.push({ name: 'new stuff' })
}
return {
...toRefs(state),
toggle,
changeType,
addChild
}
}
}
</script>
<p>(You can double click on an item to turn it into a folder.)</p>
<!-- the app root element -->
<ul id="demo">
<tree-item class="item" :model="treeData"></tree-item>
</ul>
<script>
const treeData = {
name: 'My Tree',
children: [
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [
{
name: 'child folder',
children: [{ name: 'hello' }, { name: 'wat' }]
},
{ name: 'hello' },
{ name: 'wat' },
{
name: 'child folder',
children: [{ name: 'hello' }, { name: 'wat' }]
}
]
}
]
}
new Vue({
components: {
TreeItem
},
data: () => ({
treeData
})
}).$mount('#demo')
</script>
<style>
body {
font-family: Menlo, Consolas, monospace;
color: #444;
}
.item {
cursor: pointer;
}
.bold {
font-weight: bold;
}
ul {
padding-left: 1em;
line-height: 1.5em;
list-style-type: dot;
}
</style>

View File

@ -93,7 +93,7 @@
"lodash.template": "^4.4.0",
"lodash.uniq": "^4.5.0",
"lru-cache": "^7.8.1",
"marked": "^3.0.8",
"marked": "^4.0.6",
"memory-fs": "^0.5.0",
"prettier": "^2.6.2",
"puppeteer": "^14.1.1",

View File

@ -32,7 +32,7 @@ specifiers:
lodash.template: ^4.4.0
lodash.uniq: ^4.5.0
lru-cache: ^7.8.1
marked: ^3.0.8
marked: ^4.0.6
memory-fs: ^0.5.0
prettier: ^2.6.2
puppeteer: ^14.1.1
@ -83,7 +83,7 @@ devDependencies:
lodash.template: 4.5.0
lodash.uniq: 4.5.0
lru-cache: 7.10.1
marked: 3.0.8
marked: 4.0.16
memory-fs: 0.5.0
prettier: 2.6.2
puppeteer: 14.1.1
@ -3747,8 +3747,8 @@ packages:
object-visit: 1.0.1
dev: true
/marked/3.0.8:
resolution: {integrity: sha512-0gVrAjo5m0VZSJb4rpL59K1unJAMb/hm8HRXqasD8VeC8m91ytDPMritgFSlKonfdt+rRYYpP/JfLxgIX8yoSw==}
/marked/4.0.16:
resolution: {integrity: sha512-wahonIQ5Jnyatt2fn8KqF/nIqZM8mh3oRu2+l5EANGMhu6RFjiSG52QNE2eWzFMI94HqYSgN184NurgNG6CztA==}
engines: {node: '>= 12'}
hasBin: true
dev: true

View File

@ -52,11 +52,11 @@ describe('e2e: commits', () => {
E2E_TIMEOUT
)
// test(
// 'composition',
// async () => {
// await testCommits('composition')
// },
// E2E_TIMEOUT
// )
test(
'composition',
async () => {
await testCommits('composition')
},
E2E_TIMEOUT
)
})

View File

@ -5,9 +5,10 @@ export function getExampleUrl(
name: string,
apiType: 'classic' | 'composition'
) {
const file = apiType === 'composition' ? `${name}.html` : `${name}/index.html`
return `file://${path.resolve(
__dirname,
`../../examples/${apiType}/${name}/index.html`
`../../examples/${apiType}/${file}`
)}`
}

View File

@ -105,11 +105,11 @@ describe('e2e: grid', () => {
E2E_TIMEOUT
)
// test(
// 'composition',
// async () => {
// await testGrid('composition')
// },
// E2E_TIMEOUT
// )
test(
'composition',
async () => {
await testGrid('composition')
},
E2E_TIMEOUT
)
})

View File

@ -36,11 +36,11 @@ describe('e2e: markdown', () => {
E2E_TIMEOUT
)
// test(
// 'composition',
// async () => {
// await testMarkdown('composition')
// },
// E2E_TIMEOUT
// )
test(
'composition',
async () => {
await testMarkdown('composition')
},
E2E_TIMEOUT
)
})

View File

@ -1,6 +1,6 @@
import { setupPuppeteer, getExampleUrl, E2E_TIMEOUT } from './e2eUtils'
declare const stats: {
declare const globalStats: {
label: string
value: number
}[]
@ -22,7 +22,7 @@ describe('e2e: svg', () => {
expect(
await page().evaluate(
total => {
const points = stats
const points = globalStats
.map((stat, i) => {
const point = valueToPoint(stat.value, i, total)
return point.x + ',' + point.y
@ -41,7 +41,7 @@ describe('e2e: svg', () => {
async function assertLabels(total: number) {
const positions = await page().evaluate(
total => {
return stats.map((stat, i) => {
return globalStats.map((stat, i) => {
const point = valueToPoint(+stat.value + 10, i, total)
return [point.x, point.y]
})
@ -60,7 +60,7 @@ describe('e2e: svg', () => {
// assert each value of stats is correct
async function assertStats(expected: number[]) {
const statsValue = await page().evaluate(() => {
return stats.map(stat => +stat.value)
return globalStats.map(stat => +stat.value)
})
expect(statsValue).toEqual(expected)
}
@ -141,11 +141,11 @@ describe('e2e: svg', () => {
E2E_TIMEOUT
)
// test(
// 'composition',
// async () => {
// await testSvg('composition')
// },
// E2E_TIMEOUT
// )
test(
'composition',
async () => {
await testSvg('composition')
},
E2E_TIMEOUT
)
})

View File

@ -172,11 +172,11 @@ describe('e2e: todomvc', () => {
E2E_TIMEOUT
)
// test(
// 'composition',
// async () => {
// await testTodomvc('composition')
// },
// E2E_TIMEOUT
// )
test(
'composition',
async () => {
await testTodomvc('composition')
},
E2E_TIMEOUT
)
})

View File

@ -98,11 +98,11 @@ describe('e2e: tree', () => {
E2E_TIMEOUT
)
// test(
// 'composition',
// async () => {
// await testTree('composition')
// },
// E2E_TIMEOUT
// )
test(
'composition',
async () => {
await testTree('composition')
},
E2E_TIMEOUT
)
})