2 Commit-ok 428a026138 ... a4829dfbf8

Szerző SHA1 Üzenet Dátum
  Developer a4829dfbf8 fix: ignore stale activity category responses 1 hete
  Developer 193d60ff4a fix: refresh claimable practice records on show 1 hete

+ 6 - 1
pages/home.vue

@@ -129,6 +129,7 @@
 	const gridIndex = ref(0)
 	const isOver = ref(false)
 	const activityList = ref([])
+	const listRequestVersion = ref(0)
 	const isTypeGridSticky = ref(false)
 	const typeGridOffsetTop = ref(0)
 	const typeGridHeight = ref(0)
@@ -180,6 +181,7 @@
 	}
 	
 	const initList = () => {
+		listRequestVersion.value++;
 		queryParams.value.page = 1;
 		isOver.value = false;
 		activityList.value = [];
@@ -239,7 +241,10 @@
 	}
 	
 	const getActivityList = () => {
-		proxy.$api.get('/core/activity/page',queryParams.value).then(({data:res})=>{
+		const requestVersion = listRequestVersion.value
+		const requestParams = { ...queryParams.value }
+		proxy.$api.get('/core/activity/page',requestParams).then(({data:res})=>{
+			if(requestVersion!==listRequestVersion.value) return
 			if(res.code!==0) return proxy.$showToast(res.msg)
 			// 只对本次新增项做格式化,避免 loadMore 时旧项(已格式化成 "yyyy.MM.dd hh:mm")
 			// 被再次 new Date() 解析为 Invalid Date 导致显示 NaN

+ 4 - 3
pagesMy/practice.vue

@@ -51,7 +51,8 @@
 	import CusHeader from '@/components/CusHeader/index.vue'
 	import PageEmpty from '@/components/pageEmpty/index.vue'
 	import PracticeBox from '@/components/pages/practiceBox/index.vue'
-	import { ref, getCurrentInstance, onMounted } from 'vue'
+	import { ref, getCurrentInstance } from 'vue'
+	import { onShow } from '@dcloudio/uni-app'
 	const { proxy } = getCurrentInstance()
 	
 	const pb = ref(184)
@@ -190,7 +191,7 @@
 		})
 	}
 	
-	onMounted(()=>{
+	onShow(()=>{
 		try{
 			queryParams.value.userId = JSON.parse(uni.getStorageSync('userInfo')).id;
 			getMemberList();
@@ -335,4 +336,4 @@
 			bottom: 64rpx;
 		}
 	}
-</style>
+</style>

+ 104 - 0
tests/home-category-filter-race.test.js

@@ -0,0 +1,104 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+import fs from 'node:fs'
+import path from 'node:path'
+import vm from 'node:vm'
+
+const homeFile = path.resolve('pages/home.vue')
+const flushPromises = () => new Promise(resolve => setImmediate(resolve))
+
+function deferred () {
+  let resolve
+  const promise = new Promise(done => { resolve = done })
+  return { promise, resolve }
+}
+
+function mountHomePage () {
+  const source = fs.readFileSync(homeFile, 'utf8')
+  const script = source.match(/<script setup[^>]*>([\s\S]*?)<\/script>/)[1]
+    .replace(/^\s*import\s+.*$/gm, '') +
+    '\nglobalThis.__home = { changeType, activityList, queryParams }'
+
+  const lifecycle = {}
+  const activityRequests = []
+  const api = {
+    get (url, params) {
+      if (url === '/core/advertisement/manage/page') {
+        return Promise.resolve({ data: { code: 0, data: { list: [] } } })
+      }
+      if (url === '/core/activity/category/list') {
+        return Promise.resolve({
+          data: {
+            code: 0,
+            data: [{ id: 'category-1', categoryName: '分类一', iconUrl: 'icon.png' }]
+          }
+        })
+      }
+      if (url === '/core/activity/page') {
+        const request = deferred()
+        activityRequests.push({ params: { ...params }, ...request })
+        return request.promise
+      }
+      throw new Error(`unexpected request: ${url}`)
+    },
+    post () {
+      return Promise.resolve({ data: { code: 0 } })
+    }
+  }
+  const selectorQuery = {
+    in () { return this },
+    select () { return this },
+    boundingClientRect () { return this },
+    exec (callback) { callback([]) }
+  }
+  const context = {
+    ref: value => ({ value }),
+    computed: getter => ({ get value () { return getter() } }),
+    getCurrentInstance: () => ({ proxy: { $api: api, $showToast: () => {} } }),
+    onMounted: callback => { lifecycle.mounted = callback },
+    nextTick: callback => callback(),
+    watch: () => {},
+    useUserStore: () => ({ token: '' }),
+    onLoad: () => {},
+    onShareAppMessage: () => {},
+    onShareTimeline: () => {},
+    onPullDownRefresh: () => {},
+    uni: {
+      getStorageSync: () => '',
+      createSelectorQuery: () => selectorQuery,
+      stopPullDownRefresh: () => {}
+    },
+    Date,
+    console
+  }
+  vm.runInNewContext(script, context)
+  lifecycle.mounted()
+
+  return { home: context.__home, activityRequests }
+}
+
+test('点击分类后忽略晚到的全部活动响应', async () => {
+  const { home, activityRequests } = mountHomePage()
+  await flushPromises()
+
+  assert.equal(activityRequests.length, 1)
+  assert.equal(activityRequests[0].params.categoryId, '')
+
+  home.changeType({ id: 'category-1' })
+  assert.equal(activityRequests.length, 2)
+  assert.equal(activityRequests[1].params.categoryId, 'category-1')
+
+  activityRequests[1].resolve({
+    data: { code: 0, data: { list: [{ id: 'filtered-activity' }] } }
+  })
+  await flushPromises()
+  assert.deepEqual(Array.from(home.activityList.value, item => item.id), ['filtered-activity'])
+
+  activityRequests[0].resolve({
+    data: { code: 0, data: { list: [{ id: 'stale-all-activity' }] } }
+  })
+  await flushPromises()
+
+  assert.deepEqual(Array.from(home.activityList.value, item => item.id), ['filtered-activity'])
+  assert.equal(home.queryParams.value.categoryId, 'category-1')
+})

+ 62 - 0
tests/practice-page-refresh.test.js

@@ -0,0 +1,62 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+import fs from 'node:fs'
+import path from 'node:path'
+import vm from 'node:vm'
+
+const practiceFile = path.resolve('pagesMy/practice.vue')
+
+const flushPromises = () => new Promise(resolve => setImmediate(resolve))
+
+function mountPracticePage () {
+  const source = fs.readFileSync(practiceFile, 'utf8')
+  const script = source.match(/<script setup[^>]*>([\s\S]*?)<\/script>/)[1]
+    .replace(/^\s*import\s+.*$/gm, '')
+
+  const lifecycle = {}
+  const requests = []
+  const api = {
+    get (url, params) {
+      requests.push({ url, params: { ...params } })
+      if (url === '/core/family/member/page') {
+        return Promise.resolve({ data: { code: 0, data: { list: [{ id: 'member-1', name: '小明' }] } } })
+      }
+      return Promise.resolve({ data: { code: 0, data: { list: [] } } })
+    },
+    post () {
+      return Promise.resolve({ data: { code: 0, data: {} } })
+    }
+  }
+
+  vm.runInNewContext(script, {
+    ref: value => ({ value }),
+    getCurrentInstance: () => ({ proxy: { $api: api, $showToast: () => {} } }),
+    onMounted: callback => { lifecycle.mounted = callback },
+    onShow: callback => { lifecycle.show = callback },
+    uni: {
+      getStorageSync: key => key === 'userInfo' ? JSON.stringify({ id: 'user-1' }) : '',
+      navigateTo: () => {}
+    },
+    Date,
+    console
+  })
+
+  return { lifecycle, requests }
+}
+
+test('申领社会实践记录页面每次显示时重新查询可申领数据', async () => {
+  const { lifecycle, requests } = mountPracticePage()
+
+  assert.equal(typeof lifecycle.show, 'function', '页面必须注册 onShow 刷新处理')
+  lifecycle.show()
+  await flushPromises()
+  await flushPromises()
+
+  assert.deepEqual(requests.map(item => item.url), [
+    '/core/family/member/page',
+    '/core/social/practice/record/claimPage'
+  ])
+  assert.equal(requests[1].params.userId, 'user-1')
+  assert.equal(requests[1].params.memberId, 'member-1')
+  assert.equal(requests[1].params.page, 1)
+})