From 93873228ff5b75496ef1cf2afae66b3c9ba73e96 Mon Sep 17 00:00:00 2001 From: javad Date: Fri, 9 Oct 2026 11:06:09 +0330 Subject: [PATCH] fix order --- src/app/(index)/shop/page.tsx | 43 +++++++--------- src/app/(index)/shop/product/[id]/page.tsx | 19 +++----- tests/shop-ordering.test.cjs | 57 ++++++++++++++++++++++ 3 files changed, 82 insertions(+), 37 deletions(-) create mode 100644 tests/shop-ordering.test.cjs diff --git a/src/app/(index)/shop/page.tsx b/src/app/(index)/shop/page.tsx index ba32237..e4ab8b1 100644 --- a/src/app/(index)/shop/page.tsx +++ b/src/app/(index)/shop/page.tsx @@ -1,37 +1,30 @@ 'use client' import ProductCategoryBox from '@/app/components/layout/shop/products-cat' -import Image from 'next/image' import axiosInstance from '@/axios/axiosInstance' -import { useEffect, useState } from 'react' -import { useQuery,useQueryClient } from '@tanstack/react-query' +import { useEffect } from 'react' +import { useQuery } from '@tanstack/react-query' const Page = ()=>{ - const [category, setCategory] = useState([]) - - const queryClient = useQueryClient() - - useEffect(()=>{ - - },[]) - const query = useQuery({ queryKey: ['categoryProducts'], queryFn: async ()=>{ - + return await axiosInstance.get("shop/v2/product-category/list/") - },refetchOnWindowFocus:false, onSuccess(result:any) { + },refetchOnWindowFocus:true, }) - setCategory(result.data) - - setTimeout(async ()=>{ - const scrollpos = localStorage.getItem('scrollY') - if (scrollpos){ - await window.scrollTo(0, parseInt(scrollpos)); + const category = query.data?.data ?? [] + useEffect(() => { + if (!query.isSuccess) return + const frame = requestAnimationFrame(() => { + try { + const position = Number(localStorage.getItem('scrollY')) + if (Number.isFinite(position) && position > 0) window.scrollTo(0, position) + } catch { + console.warn('Could not restore the saved shop scroll position.') } - - },150) - - }, }) + }) + return () => cancelAnimationFrame(frame) + }, [query.isSuccess]) if(query.isLoading){ return ( @@ -53,10 +46,10 @@ const Page = ()=>{ {query.isSuccess?category.map((iteam:any)=>{ return }):null} - + ) } -export default Page \ No newline at end of file +export default Page diff --git a/src/app/(index)/shop/product/[id]/page.tsx b/src/app/(index)/shop/product/[id]/page.tsx index d575257..1d8bc40 100644 --- a/src/app/(index)/shop/product/[id]/page.tsx +++ b/src/app/(index)/shop/product/[id]/page.tsx @@ -1,11 +1,9 @@ 'use client' import Products from '@/app/components/layout/shop/products' import Image from 'next/image' -import Link from 'next/link' import { useRouter } from 'next/navigation' -import { useQuery, useQueryClient } from '@tanstack/react-query' +import { useQuery } from '@tanstack/react-query' import axiosInstance from '@/axios/axiosInstance' -import { useState } from 'react' import { Table, TableBody, @@ -21,16 +19,13 @@ const Page = ({ params }: { params: { id: string } })=>{ const router = useRouter(); - const [products, setProducts] = useState<{category_name: string, category_img: string | null, - products:[{id:number, name:string, price: number, view_only: boolean}], - view_only_products:[{id:number, name:string, price: number, view_only: boolean}] - }>() - const query = useQuery({ queryKey: ['productListByCat', params.id], queryFn: async ()=>{ - return await axiosInstance.get(`shop/product/list/${params.id}/`) - }, onSuccess(result:any) { - setProducts(result.data) + return await axiosInstance.get<{category_name: string, category_img: string | null, + products: {id:number, name:string, price:number, view_only:boolean}[], + view_only_products: {id:number, name:string, price:number, view_only:boolean}[] | null + }>(`shop/product/list/${params.id}/`) }, }); + const products = query.data?.data if(query.isLoading){ return ( @@ -115,4 +110,4 @@ const Page = ({ params }: { params: { id: string } })=>{ } -export default Page \ No newline at end of file +export default Page diff --git a/tests/shop-ordering.test.cjs b/tests/shop-ordering.test.cjs new file mode 100644 index 0000000..86c6ee9 --- /dev/null +++ b/tests/shop-ordering.test.cjs @@ -0,0 +1,57 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const vm = require('node:vm'); +const ts = require('typescript'); + +// Render the actual pages with cached React Query responses; no onSuccess call. +function pageHarness(filename, data) { + const state = { data: { data }, isSuccess: true, isLoading: false, isError: false, dataUpdatedAt: 1 }; + const module = { exports: {} }; let options; + const source = ts.transpileModule(fs.readFileSync(filename, 'utf8'), { compilerOptions: { + module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, + esModuleInterop: true, jsx: ts.JsxEmit.ReactJSX, + } }).outputText; + const requireStub = name => { + if (name === 'react/jsx-runtime') return require(name); + if (name === 'react') return { useEffect() {}, useState: () => [undefined, () => {}] }; + if (name === '@tanstack/react-query') return { useQuery: value => { options = value; return state; } }; + if (name === 'next/navigation') return { useRouter: () => ({ push() {} }) }; + if (name === '@/axios/axiosInstance') return { __esModule: true, default: { get() {} } }; + if (name === '@/components/ui/table') return Object.fromEntries(['Table', 'TableBody', 'TableCaption', 'TableCell', 'TableFooter', 'TableHead', 'TableHeader', 'TableRow'].map(key => [key, key])); + return { __esModule: true, default: name.endsWith('products-cat') ? 'CategoryCard' : name.endsWith('products') ? 'ProductCard' : 'Image' }; + }; + vm.runInNewContext('(function(require,module,exports){' + source + '\n})', {})(requireStub, module, module.exports); + return { state, render: () => module.exports.default({ params: { id: '9' } }), get options() { return options; } }; +} +function elements(tree, type) { + if (tree == null || typeof tree !== 'object') return []; + if (Array.isArray(tree)) return tree.flatMap(item => elements(item, type)); + return [...(tree.type === type ? [tree] : []), ...elements(tree.props?.children, type)]; +} +const categoryPage = 'src/app/(index)/shop/page.tsx'; +const productPage = 'src/app/(index)/shop/product/[id]/page.tsx'; + +test('shop displays cached categories in the API order, including after updated data', () => { + const h = pageHarness(categoryPage, [{ id: 8, name: 'Second alphabetically' }, { id: 1, name: 'First alphabetically' }]); + assert.deepEqual(elements(h.render(), 'CategoryCard').map(card => card.props.iteam.id), [8, 1]); + h.state.data = { data: [{ id: 1 }, { id: 8 }] }; h.state.dataUpdatedAt++; + assert.deepEqual(elements(h.render(), 'CategoryCard').map(card => card.props.iteam.id), [1, 8]); + assert.equal(h.options.refetchOnWindowFocus, true); +}); +test('category page preserves cached and refreshed product order for both product groups', () => { + const data = { category_name: 'Category', category_img: null, + products: [{ id: 20, name: 'Z', view_only: false }, { id: 2, name: 'A', view_only: false }], + view_only_products: [{ id: 19, name: 'Custom Z' }, { id: 1, name: 'Custom A' }] }; + const h = pageHarness(productPage, data); + assert.deepEqual(elements(h.render(), 'ProductCard').map(card => card.props.iteam.id), [20, 2]); + assert.deepEqual(elements(h.render(), 'TableCell').filter(cell => cell.props.children !== '-----').map(cell => cell.props.children), ['Custom Z', 'Custom A']); + h.state.data = { data: { ...data, products: [...data.products].reverse(), view_only_products: [...data.view_only_products].reverse() } }; + assert.deepEqual(elements(h.render(), 'ProductCard').map(card => card.props.iteam.id), [2, 20]); + assert.deepEqual(elements(h.render(), 'TableCell').filter(cell => cell.props.children !== '-----').map(cell => cell.props.children), ['Custom A', 'Custom Z']); +}); +test('a category without custom products renders its ordered ordinary products', () => { + const h = pageHarness(productPage, { category_name: 'Category', category_img: null, products: [{ id: 3, view_only: false }], view_only_products: null }); + assert.deepEqual(elements(h.render(), 'ProductCard').map(card => card.props.iteam.id), [3]); + assert.equal(elements(h.render(), 'TableRow').length, 0); +});