I am trying to make a social media type app and am currently trying to implement a search feature to be able to find friends. I have the below code, which is working sometimes and not working other times (really frustrating!) I am pretty sure this is to do with the asynchronous nature of javascript. I thought I would have fixed that by putting the second find within a then function. The code should search to check against the users first name and then search against the users last name and push that into an array called users which is then flattened and edited to only have unique user objects. This is then passed to handlebars which displays the users. The issue seems to come for the following tests, which are the ones which handle more than one word, the second word is sometimes searched for and sometimes not:
- A user's search returns results for each word
- A search is not affected by adding non alphanumeric characters
controller
const User = require("../models/user");
const SearchController = {
Index: (req, res) => {
const users = [];
User.find().then((user) => {
users.push(user);
const merged = [].concat.apply([], users);
const uniqueArray = merged.filter((value, index) => {
const _value = JSON.stringify(value);
return (
index ===
merged.findIndex((obj) => {
return JSON.stringify(obj) === _value;
})
);
});
res.render("search/index", { users: uniqueArray });
});
},
Create: (req, res) => {
const searchArray = req.body.message.replace(/[\W_] /g, " ").split(/[ ,] /);
const users = [];
searchArray.forEach((name) => {
User.find({
firstName: { $regex: name, $options: "i" },
// lastName: { $regex: name, $options: "i" },
}).then((user) => {
users.push(user);
User.find({
// firstName: { $regex: name, $options: "i" },
lastName: { $regex: name, $options: "i" },
}).then((user) => {
users.push(user);
const merged = [].concat.apply([], users);
const uniqueArray = merged.filter((value, index) => {
const _value = JSON.stringify(value);
return (
index ===
merged.findIndex((obj) => {
return JSON.stringify(obj) === _value;
})
);
});
res.render("search/index", { users: uniqueArray });
});
});
});
},
};
module.exports = SearchController;
cypress tests
describe("Searching users", () => {
beforeEach(() => {
cy.signUp();
cy.signUp("Chris", "Coding", "[email protected]", "12345");
cy.signUp("Paul", "Coding", "[email protected]", "12345");
cy.signUp("Adam", "Woodcock", "[email protected]", "12345");
cy.signUp("Kathleen", "Woodcock", "[email protected]", "12345");
cy.signUp("George", "Hett", "[email protected]", "12345");
cy.signUp("Rob", "Oman", "[email protected]", "12345");
});
it("A user can search for other users", () => {
cy.login();
cy.get("#searchBox").type("george");
cy.get("#searchButton").click();
cy.get(".user-container").first().should("contain", "George Hett");
});
it("A user can search for other users by last name", () => {
cy.login();
cy.get("#searchBox").type("woodcock");
cy.get("#searchButton").click();
cy.get(".user-container").should("have.length", 2);
cy.get(".user-container").should("contain", "Adam Woodcock");
cy.get(".user-container").should("contain", "Kathleen Woodcock");
});
it("A blank search returns all users", () => {
cy.login();
cy.get("#searchButton").click();
cy.get(".user-container").should("have.length", 7);
cy.get(".user-container").should("contain", "Adam Woodcock");
cy.get(".user-container").should("contain", "Kathleen Woodcock");
cy.get(".user-container").should("contain", "Chris Coding");
cy.get(".user-container").should("contain", "Paul Coding");
cy.get(".user-container").should("contain", "Rob Oman");
cy.get(".user-container").should("contain", "George Hett");
cy.get(".user-container").should("contain", "Barry Barry-Barroldsson");
});
it("Visiting the search page without going through the search bar returns all users", () => {
cy.login();
cy.visit("/search");
cy.get(".user-container").should("have.length", 7);
cy.get(".user-container").should("contain", "Adam Woodcock");
cy.get(".user-container").should("contain", "Kathleen Woodcock");
cy.get(".user-container").should("contain", "Chris Coding");
cy.get(".user-container").should("contain", "Paul Coding");
cy.get(".user-container").should("contain", "Rob Oman");
cy.get(".user-container").should("contain", "George Hett");
cy.get(".user-container").should("contain", "Barry Barry-Barroldsson");
});
it("A search is not affected by adding non alphanumeric characters", () => {
cy.login();
cy.get("#searchBox").type("Chris /.,[] Coding");
cy.get("#searchButton").click();
cy.get(".user-container").should("have.length", 2);
cy.get(".user-container").should("contain", "Chris Coding");
cy.get(".user-container").should("contain", "Paul Coding");
});
it("A user's search returns results for each word", () => {
cy.login();
cy.get("#searchBox").type("Chris Woodcock");
cy.get("#searchButton").click();
cy.get(".user-container").should("have.length", 3);
cy.get(".user-container").should("contain", "Adam Woodcock");
cy.get(".user-container").should("contain", "Kathleen Woodcock");
cy.get(".user-container").should("contain", "Chris Coding");
});
});
Please can you recommend what adjustments I can make to get this code functioning correctly
CodePudding user response:
You may find this works better, although I can't find anything prone to failure due to async processing.
I'm assuming User.find()
returns a promise.
const promises = searchArray.map((name) => {
return User.find({
firstName: { $regex: name, $options: "i" },
lastName: { $regex: name, $options: "i" },
})
})
Promise.all(promises).then((values) => {
const uniqueArray = values
.flat()
.filter((value, index, arr) => arr.indexOf(value) === index)
res.render("search/index", { users: uniqueArray });
})
The cy.get(".user-container").should("have.length", 3);
should take care of any lag on the test end, although you may want to bump up the timeout if it's likely to take longer than 4 seconds to perform the multi-level search.
cy.get(".user-container", {timeout:10000})
.should("have.length", 3);
CodePudding user response:
I managed to solve my own question by refactoring the above to this, by changing the for each loop to build the final query and passing that through to a find statement and then rendering worked really nicely. I also refactored the index route.
const User = require("../models/user");
const SearchController = {
Index: (req, res) => {
User.find().then((users) => {
res.render("search/index", { users: users });
});
},
Create: (req, res) => {
const searchArray = req.body.message.replace(/[\W_] /g, " ").split(/[ ,] /);
const findQuery = [];
searchArray.forEach((name) => {
const first = { firstName: { $regex: name, $options: "i" } };
const second = { lastName: { $regex: name, $options: "i" } };
findQuery.push(first, second);
});
User.find({ $or: findQuery }).then((users) => {
res.render("search/index", { users: users });
});
},
};
module.exports = SearchController;