Closed annewanghy closed 6 years ago
VSCode 忽略搜索哪些文件夹
"search.exclude": {
"**/.git": true,
"**/build": true,
"**/node_molecules": true,
"**/bower_components": true,
"**/tmp": true
}
use Hugo to make a blog based on go quicker than Hexo
frontend/containers/product/index.tsx
Product.getInitialProps = ({
query,
context
}: InitialProps): Promise<ProductProps> => {
const productCode: api.IProductCode = { code: (query && query.code) || "" };
if (!productCode.code) {
throw errNotFound;
}
const service = makeService(api.ProductService, context);
return service.getProduct(productCode).then(productResponse => {
if (!productResponse.product) {
throw errNotFound;
}
const properties: base.IProperty[] =
(productResponse.product && productResponse.product.filterProperties) ||
[];
const topLevelCategory: base.IProperty = properties.find(
({ field }) => field === "TopLevelCategory"
) || { values: [] };
const filterProperties: products.IFilterProperty[] = [
{
field: "TopLevelCategory",
groupType: products.FilterGroupType.PRODUCT,
inValues: topLevelCategory.values
}
];
return service
.filterProducts({ filterProperties, page: 1, perPage: 5 })
.then(({ result, rdGroups }) => {
const products = ((result && result.products) || [])
.filter(({ code }) => code !== productCode.code)
.slice(0, 4);
const productsResponse: api.IProductsResponse = {
products,
rdGroups
};
return {
productResponse,
productsResponse
} as ProductProps;
});
});
};
api/products.proto
api.ProductService
上面用到了GetProduct (ProductCode) returns (ProductResponse)
这个方法
service ProductService {
rpc GetProduct (ProductCode) returns (ProductResponse);
rpc GetProducts (ProductCodes) returns (ProductsResponse);
rpc GetFilterGroups (products.SearchOptions) returns (FilterProductsResponse);
rpc FilterProducts (products.SearchOptions) returns (FilterProductsResponse);
}
没有找到,就throw一个errorNotFound的错误, 输出错误信息product not found
const errNotFound = new ProductNotFound(404);
class ProductNotFound extends Error {
constructor(public statusCode: number) {
super("product not found");
}
}
frontend/proto.d.ts文件里面可以找到base.IProperty
export namespace base {
/** Properties of a Property. */
interface IProperty {
/** Property field */
field?: string;
/** Property values */
values?: string[];
}
...
}
/** for filter products */
namespace products下的interface IProduct有这个过滤属性,类型就是base.IProperty
filterProperties?: base.IProperty[];
const properties: base.IProperty[] =
(productResponse.product && productResponse.product.filterProperties) ||
[];
而base.IProperty是namespace base下的interface IProperty,这个接口需要两个可选参数filed,和values
const topLevelCategory: base.IProperty = properties.find(
({ field }) => field === "TopLevelCategory") || { values: [] };
设置过滤条件为*TopLevelCategory*, 需要修改为*TopLevelCollection*
const filterProperties: products.IFilterProperty[] = [
{
field: "TopLevelCategory",
groupType: products.FilterGroupType.PRODUCT,
inValues: topLevelCategory.values
}
];
class SearchOptions { //选择条件
class AggregateCondition {//合计条件
interface IFilterProperty { // 过滤属性
/** FilterProperty field */
field?: string;
/** FilterProperty groupType */
groupType?: products.FilterGroupType;
/** FilterProperty inValues */
inValues?: string[];
/** FilterProperty isNegative */
isNegative?: boolean;
}
}
}
return service
.filterProducts({ filterProperties, page: 1, perPage: 5 }) // 用到了page, perPage和filterProperties三个可选参数
.then(({ result, rdGroups }) => {// 用到了返回函数的result和rdGroups两个参数
const products = ((result && result.products) || []) // 得到返回的结果是result.products
.filter(({ code }) => code !== productCode.code)
.slice(0, 4);
const productsResponse: api.IProductsResponse = {
products,
rdGroups
};
return {
productResponse,
productsResponse
} as ProductProps;
});
ProductService{
//返回一个Promise对象
public filterProducts(request: products.ISearchOptions): Promise<api.FilterProductsResponse>;
}
输入参数
products.ISearchOptions
/** Properties of a SearchOptions. */
interface ISearchOptions {
/** SearchOptions keyword */
keyword?: string;
/** SearchOptions page */
page?: (number|Long);
/** SearchOptions perPage */
perPage?: (number|Long);
/** SearchOptions filterProperties */
filterProperties?: products.IFilterProperty[];
/** SearchOptions sorters */
sorters?: products.ISorter[];
/** SearchOptions conditions */
conditions?: products.FilterCondition[];
/** SearchOptions filterGroups */
filterGroups?: products.ISearchFilterGroup[];
/** SearchOptions filterGroupMode */
filterGroupMode?: products.SearchFilterGroupMode;
/** SearchOptions path */
path?: string;
/** SearchOptions publishReady */
publishReady?: (number|Long);
/** SearchOptions scheduledAt */
scheduledAt?: string;
}
返回参数
class FilterProductsResponse {
/**
* Constructs a new FilterProductsResponse.
* @param [properties] Properties to set
*/
constructor(properties?: api.IFilterProductsResponse);
/** FilterProductsResponse result. */
public result?: (products.IFilterProductsResult|null);
/** FilterProductsResponse rdGroups. */
public rdGroups: referencedata.IRDGroup[];
...
}
/** Properties of a FilterProductsResult. */
interface IFilterProductsResult {
/** FilterProductsResult products */
products?: products.IProduct[];
/** FilterProductsResult filterGroups */
filterGroups?: products.IFilterGroup[];
/** FilterProductsResult total */
total?: (number|Long);
}
主要是解决将topLevelCategory换成topLevelCollection的问题
frontend/product/productField.ts
enum ProductField {
TopLevelCategory = "TopLevelCategory",
SubCategory = "SubCategory",
Collection = "Collection",
Material = "Material",
ColorDescription = "ColorDescription",
Size = "Size",
TotalLength = "TotalLength",
MaximumWidth = "MaximumWidth"
}
TopLevelCategory
{"categories":{"items":[{"itemType":"TopLevelCategory","code":"TC1","slug":"earrings","name":"ピアス(イヤリング)","description":"多くの文化で身分や美しさの象徴として用いられている。かつては、奴隷身分を示すため、耳から外すことのできないタイプのものが用いられていたところもある。","images":[{"device":"MOBILE","url":"//mastani-dev.s3-ap-southeast-1.amazonaws.com/reference_data_item_images/6/image/key-visual-1.20171109052617018896809.png"},{"device":"LAPTOP","url":"//mastani-dev.s3-ap-southeast-1.amazonaws.com/reference_data_item_images/5/image/key-visual.20171109054317787731532.png","purpose":"earrings category"}]},{"itemType":"TopLevelCategory","code":"TC2","slug":"rings","name":"リング"},{"itemType":"TopLevelCategory","code":"TC3","slug":"necklace","name":"ネックレス"},{"itemType":"TopLevelCategory","code":"TC4","slug":"bracelet","name":"ブレスレット"},{"itemType":"TopLevelCategory","code":"TC5","slug":"brooch","name":"ブローチ"},{"itemType":"TopLevelCategory","code":"TC6","slug":"pendant","name":"ペンダント"},{"itemType":"TopLevelCategory","code":"TC7","slug":"brooch","name":"ブローチ"},{"itemType":"TopLevelCategory","code":"TC8","slug":"accessories","name":"アクセサリー","images":[{"device":"MOBILE","url":"//mastani-dev.s3.ap-southeast-1.amazonaws.com/reference_data_item_images/1/image/anime-like-fate-stay-night.20171102081417498370912.jpg","purpose":"collection page"}]}]},"isOnCartPage":false}
frontend/index.ts
const CollectionList = (limit?: number) => {
const List = ((props: Props) => (
<Responsive>
{(desktop: boolean) =>
desktop ? <DesktopList {...props} /> : <MobileList {...props} />}
</Responsive>
)) as Container<Props>;
List.getInitialProps = ({ context }: InitialProps): Promise<Props> =>
makeService(api.ReferenceDataService, context)
.getItems({ fetchAll: true, itemType: "Collection" })
.then(({ items }) => {
items = items.filter(
({ images }) => images != null && images.length > 0
);
if (limit != null) {
items = items.slice(0, limit);
}
return { items };
});
return List;
};
TopLevelCategory
{filterProperties: Array(1), page: 1, perPage: 5}
filterProperties:Array(1)
field:"TopLevelCategory"
groupType:0
inValues:"TC1"
Collection
{filterProperties: Array(1), page: 1, perPage: 5}
filterProperties:Array(1)
field:"Collection"
groupType:0
inValues:["CL4"]
但是最后和Jaden讨论之后,明白产品没有collection这个属性,而是用TopLevelCategory. 那就是不需要修改原来junhui写的内容
学习怎么使用postman调用后台数据 导入团队开发的.json文件,然后换到collection的tab页面,发送send请求就可以获取API的数据
本地的API数据 http://localhost:9800/api/ja/api.ProductService/FilterProducts 更换为远程的API数据 https://mastani-api.dt.theplant-dev.com/api/ja/api.ProductService/FilterProducts
Promise没有完成 product/index.test.tsx 现在的写法用then来调用filter是错误的,因此还需要修改 半成品
import Product from ".";
import { ProductProps } from "./mobile";
import { api, products, base } from "../../proto";
import { productResponse, productsResponse } from "./fakeData";
import { testContext } from "../../testHelper";
const properties: base.IProperty[] =
(productResponse.product && productResponse.product.filterProperties) ||
[];
const topLevelCategory: base.IProperty = properties.find(
({ field }) => field === "TopLevelCategory"
) || { values: [] };
const filterProperties: products.IFilterProperty[] = [
{
field: "TopLevelCategory",
groupType: products.FilterGroupType.PRODUCT,
inValues: topLevelCategory.values
}
];
describe("Product.getInitialProps", () => {
test("happy filter path", async () => {
const response: typeof api.ProductService.prototype.getProduct(productResponse.product.code) = () =>
Promise
.resolve(new api.ProductResponse(productResponse))
.then(() => {
const filterResponse: typeof api.ProductService.prototype.filterProducts({ filterProperties, page: 1, perPage: 5 })= () =>
Promise.resolve(new api.FilterProductsResponse(productsResponse))
})
api.ProductService.prototype.getProduct = jest.fn(response);
const props = await Product.getInitialProps(testContext());
const expected: ProductProps = { productResponse: productResponse, productsResponse: productsResponse};
expect(props).toMatchObject(expected);
});
test("failing filter API call", () => {
const error = new Error("test error");
api.ProductService.prototype.filterProducts = jest.fn(() =>
Promise.reject(error)
);
expect.hasAssertions();
return expect(Product.getInitialProps(testContext())).rejects.toBe(error);
});
test("failing API call", () => {
const error = new Error("test error");
api.ProductService.prototype.getProduct = jest.fn(() =>
Promise.reject(error)
);
expect.hasAssertions();
return expect(Product.getInitialProps(testContext())).rejects.toBe(error);
});
});
import Product from ".";
import { ProductProps } from "./mobile";
import { api } from "../../proto";
import { productResponse, productsResponse } from "./fakeData";
import { testContext } from "../../testHelper";
describe("Product.getInitialProps", () => {
test("happy path", async () => {
const getProductResponse: typeof api.ProductService.prototype.getProduct = () =>
Promise.resolve(new api.ProductResponse(productResponse));
const getProductsResponse: typeof api.ProductService.prototype.getFilterGroups = () =>
Promise.resolve(new api.FilterProductsResponse(productsResponse));
api.ProductService.prototype.getProduct = jest.fn(getProductResponse);
api.ProductService.prototype.getFilterGroups = jest.fn(getProductsResponse);
const props = await Product.getInitialProps({
...testContext(),
query: { code: "xxx" }
});
const expected: ProductProps = {
productResponse: productResponse,
productsResponse: productsResponse
};
expect(props).toMatchObject(expected);
});
test("failing API call", () => {
const error = new Error("test error");
api.ProductService.prototype.getProduct = jest.fn(() =>
Promise.reject(error)
);
api.ProductService.prototype.getFilterGroups = jest.fn(() =>
Promise.reject(error)
);
expect.hasAssertions();
return expect(
Product.getInitialProps({ ...testContext(), query: { code: "xxx" } })
).rejects.toBe(error);
});
});
总共12个case
first try
import Product from ".";
import { ProductProps } from "./mobile";
import { api } from "../../proto";
import {
productResponse,
filterProductsResponse,
filteredProductsResponse
} from "./fakeData";
import { testContext } from "../../testHelper";
describe("throw error", () => {
test("fail to get product code", () => {
const error = new Error("fail to get product code");
expect.hasAssertions();
return expect(
Product.getInitialProps({
...testContext()
})
).rejects.toEqual(error);
});
test("fail to make service", () => {
const error = new RangeError("index out of range: 5 + 4 > 7");
expect.hasAssertions();
return expect(
Product.getInitialProps({
...testContext(),
query: {
code: "ES-43"
}
})
).rejects.toEqual(error);
});
test("fail to call api getProduct", () => {
const error = new Error("getProduct error");
api.ProductService.prototype.getProduct = jest.fn(() =>
Promise.reject(error)
);
expect.hasAssertions();
return expect(
Product.getInitialProps({
...testContext(),
query: {
code: "ES-43"
}
})
).rejects.toEqual(error);
});
test("fail to call api getFilterGroups", () => {
const error = new Error("getProduct error");
api.ProductService.prototype.getFilterGroups = jest.fn(() =>
Promise.reject(error)
);
expect.hasAssertions();
return expect(
Product.getInitialProps({
...testContext(),
query: {
code: "ES-43"
}
})
).rejects.toEqual(error);
});
test("failing API call", () => {
const error = new Error("test error");
api.ProductService.prototype.getProduct = jest.fn(() =>
Promise.reject(error)
);
api.ProductService.prototype.getFilterGroups = jest.fn(() =>
Promise.reject(error)
);
expect.hasAssertions();
return expect(
Product.getInitialProps({
...testContext(),
query: {
code: "ES-43"
}
})
).rejects.toEqual(error);
});
});
describe("Product.getInitialProps", () => {
test("happy path", async () => {
const getProductResponse: typeof api.ProductService.prototype.getProduct = () =>
Promise.resolve(new api.ProductResponse(productResponse));
const getFilterProductsResponse: typeof api.ProductService.prototype.filterProducts = () =>
Promise.resolve(new api.FilterProductsResponse(filterProductsResponse));
api.ProductService.prototype.getProduct = jest.fn(getProductResponse);
api.ProductService.prototype.filterProducts = jest.fn(
getFilterProductsResponse
);
const props = await Product.getInitialProps({
...testContext(),
query: {
code: "ES-43"
}
});
const expected: ProductProps = {
productResponse: productResponse,
productsResponse: filteredProductsResponse
};
expect(props).toMatchObject(expected);
});
});
describe("test filter data", () => {
const cases: Array<
[string, string | null, Array<String> | null, {} | any]
> = [
[
"filtered products don't contain the product",
"ES-00",
["ES-01", "ES-02", "ES-03"],
["ES-01", "ES-02", "ES-03"]
],
[
"filtered products contain the product",
"ES-00",
["ES-00", "ES-01", "ES-02"],
["ES-01", "ES-02"]
],
["filtered products = null", null, null, null],
[
"filtered products's length <= 4 and doesn't contain product",
"ES-00",
["ES-01"],
["ES-01"]
],
[
"filtered products'length>4 and doesn't contain the product",
"ES-00",
["ES-01", "ES-02", "ES-03", "ES-04", "ES-05"],
["ES-01", "ES-02", "ES-03", "ES-04"]
]
];
cases.forEach(([desc, productCode, filterProduct, filteredProduct]) => {
test(`${desc}`, () => {
var filterProductResult: Array<String> | null = new Array();
if (filterProduct && filterProduct.length <= 4) {
filterProductResult = ((filterProduct && filterProduct) || []).filter(
product => product !== productCode
);
} else {
filterProductResult = ((filterProduct && filterProduct) || [])
.filter(product => product !== productCode)
.slice(0, 4);
}
expect(filterProductResult).toMatchObject(filteredProduct);
});
});
});
两个错误“Expected at least one assertion to be called but received none.” 一个不匹配
TopLevelCategory -> Collectionfrontend/containers/popupShops:
frontend/containers/popupShops.test.js 主要测试 Promise对象,resolve和reject。resolve表示成功,reject表示被拒绝
frontend/proto.d.ts, 类型声明文件, PopupShopService里面有一个all方法,返回一个Promise对象
frontend/proto.js,实现文件, 用变量
PopUpShopService.prototype.all
调用all
方法api/popupshop.proto 后端文件有一个service类型的PopUpShopService,有一个rpc类型的All方法,返回一个PopupList
错误处理文件在frontend/prottp/index.ts文件下 三个错误,一个networkerror表示网络错误,可以通过Promise的reject得到 而其他两个错误是得到数据之后,验证错误提示哪一个field出错和Authorication权限错误 400以上的都是http错误