I'm writing a small CLI in typescript and I have a command which basically allows me to generate a json file with default values in it (just like npm init -y), but I don't know how to auto answer the questions in inquirer.
This is what I've got so far:
export const initializeConfig = (project: string, ...args: boolean[]) => {
prompt([
{
type: "input",
name: "name",
message: "What is the name of the project?",
default: basename(cwd()),
when: () => args.every((arg) => arg === false),
},
{
type: "list",
name: "project",
message: "What is the type of the project?",
choices: ["Node", "Python"],
default: project,
when: () => args.every((arg) => arg === false),
},
])
.then((answers: Answers) => {
config = setConfig({ name: answers.name });
config = setConfig({ project: answers.project });
})
.then(() =>
prompt([
{
type: "input",
name: "path",
message: "Where is your project root located?",
default: ".",
when: () => args.every((arg) => arg === false),
},
{
type: "input",
name: "ignore",
message: "What do you want to ignore? (comma separated)",
default: defaultIgnores(config.project).ignore,
when: () => args.every((arg) => arg === false),
},
]).then((answers: Answers) => {
config = setConfig(ignoreFiles(config.project, answers.ignore));
createConfig(answers.path, config);
})
);
};
I thought that if I'd skip/hide the questions with when(), it would use the default values, but it doesn't. It's always undefined.
Didn't find this topic on the internet so far. Any ideas?
Kind of a life hack, but I managed to "auto answer" my questions in inquirer by creating a defaults() function that returns an object of the default values.
Then I can use those if my answer object is empty as you see below:
const defaults = (project: string) => {
return {
name: basename(cwd()),
project,
path: ".",
ignore: defaultIgnores(project).ignore,
};
};
export let config: any = {
version,
};
export const initializeConfig = (project: string, ...args: boolean[]) => {
prompt([
{
type: "input",
name: "name",
message: "What is the name of the project?",
default: defaults(project).name,
when: () => args.every((arg) => arg === false),
},
{
type: "list",
name: "project",
message: "What is the type of the project?",
choices: ["Node", "Python"],
default: defaults(project).project,
when: () => args.every((arg) => arg === false),
},
])
.then((answers: Answers) => {
const { name, project: projectName } = defaults(project);
config = setConfig({ name: answers.name || name });
config = setConfig({ project: answers.project || projectName });
})
.then(() =>
prompt([
{
type: "input",
name: "path",
message: "Where is your project root located?",
default: defaults(project).path,
when: () => args.every((arg) => arg === false),
},
{
type: "input",
name: "ignore",
message: "What do you want to ignore? (comma separated)",
default: defaults(project).ignore,
when: () => args.every((arg) => arg === false),
},
]).then((answers: Answers) => {
const { ignore, path } = defaults(project);
config = setConfig(
ignoreFiles(config.project, (answers.ignore || ignore)!)
);
createConfig(answers.path || path, config);
})
);
};
Related
I'm not sure which type to designate here.
"HowitterObject" in setHowitters is data and "...prev' is the continuous addition of data from howitterObject.
interface IhowitterMessage {
message: string;
createAt: number;
id: string;
}
const Home = () => {
const [howitters, setHowitters] = useState<IhowitterMessage[]>([]);
const getHowitters = async () => {
const dbHowitter = await dbService.collection("howitter").get();
dbHowitter.forEach((document) => {
const howitterObject = {
...document.data(),
id: document.id,
};
setHowitters((prev: ???) => [howitterObject, ...prev]); //What should I put in the "prev" type?
});
};
useEffect(() => {
getHowitters();
}, []);
return(
<div>
{howitters.map((howitter: IhowitterMessage) => (
<div key={howitter.id}>
<h4>{howitter.message}</h4>
</div>
))}
</div>
);
};
If you console.log(howitters), it is as follows.
(3) [{...}, {...}, {...}]
0: {message: "no", createAt: 1631367025550, id: "q2d9TTgh36mgFZwMQ5EA"}
1: {createAt: 1631365463319, message: "Good", id: "nCABFp1v3dP73gIbckpp"}
2: {message: "hey", createAt: 1631367021665, id: "dmNBa6C8NwhDQDYDOP36"}
It's similar to your state's type
Because prev is your previous state of howitters so you could put IhowitterMessage[] there too:
setHowitters((prev: IhowitterMessage[]) =>
I am trying to test my SEO component which looks like this:
export const Seo: React.FC<Props> = ({ seo, title, excerpt, heroImage }) => {
const description = seo?.description || excerpt
const pageTitle = seo?.title || title
const router = useRouter()
return (
<NextSeo // https://www.npmjs.com/package/next-seo
canonical={seo?.canonical}
nofollow={seo?.nofollow}
noindex={seo?.noindex}
title={pageTitle}
description={description}
openGraph={{
title,
description,
type: "article",
...
and my test is like so:
describe("Seo", () => {
it("should render the meta tags", async () => {
const props = {
title: "title page",
excerpt: "string",
seo: {
title: "seo title",
description: "meta description",
},
heroImage: {
src: "url",
alt: "alt text",
width: 300,
height: 400,
},
}
function getMeta(metaName: string) {
const metas = document.getElementsByTagName("meta")
for (let i = 0; i < metas.length; i += 1) {
if (metas[i].getAttribute("name") === metaName) {
return metas[i].getAttribute("content")
}
}
return ""
}
render(<Seo {...props} />)
await waitFor(() => expect(getMeta("title")).toEqual("title page"))
})
})
however the test is failing: (it looks like the head element is empty)
I was having the same issue, but I found this answer on GitHub.
So basically you need to mock next/head, pass document.head to the container property of render's options and finally access the document.
Your test would end up like this:
jest.mock('next/head', () => {
return {
__esModule: true,
default: ({ children }: { children: Array<React.ReactElement> }) => {
return <>{children}</>;
},
};
});
describe("Seo", () => {
it("should render the meta tags", () => {
const props = {
title: "title page",
excerpt: "string",
seo: {
title: "seo title",
description: "meta description",
},
heroImage: {
src: "url",
alt: "alt text",
width: 300,
height: 400,
},
}
render(<Seo {...props} />, { container: document.head })
expect(document.title).toBe("title page")
})
})
In my case I didn't test it with that getMeta function but I believe it would work too.
in JS
jest.mock('next/head', () => {
return {
__esModule: true,
default: ({ children }) => {
return children;
}
};
});
I have the following code snippet from my component where I generate Input field according to the objects in the state.
I can successfully generate the input fields but have been getting error message:
TypeError: Cannot read property 'map' of undefined
Pointing to the method arrayObjToArrary in Utils.js whenever I type in the input field.
How can I update the value of here ??
Main.js
import Input from "../UI/Input";
import {arrayObjToArrary} from "../../utility/Utils.js";
const [profiles, setProfiles] = useState({
controls: [
{
network: {
elementType: "input",
elementConfig: {
type: "text",
label: "Network",
},
value: "Twitter",
},
},
{
username: {
elementType: "input",
elementConfig: {
type: "text",
label: "Username",
},
value: "#john",
},
},
{
url: {
elementType: "input",
elementConfig: {
type: "url",
label: "URL",
},
value: "https://example.xyz",
},
},
],
});
const profilesControls = arrayObjToArrary(profiles.controls);
const arrayInputHandler = (event, index, identifier) => {
const list = [...profiles.controls];
list[index][identifier] = event.target.value;
setProfiles(list);
};
let profileField = profilesControls.map((formElement) => (
<Input
label={formElement.config.elementConfig.label}
key={formElement.index}
type={formElement.config.elementType}
elementConfig={formElement.config.elementConfig}
value={formElement.config.value}
changed={(event) => arrayInputHandler(event, formElement.index, formElement.id)}
/>
));
Utils.js
export const arrayObjToArrary = (controls) => {
const formElementsArray = controls.map((item,index) =>({
id: Object.keys(item)[0],
index:index,
config: item[Object.keys(item)[0]],
}))
return formElementsArray;
}
You can try this
const arrayInputHandler = (event, index, identifier) => {
const list = [...profiles.controls];
list[index][identifier].value = event.target.value;
setProfiles({ controls: list });
};
check here codesandbox
The issue in how you update your profiles object in arrayInputHandler. When you pass in the list to setProfiles, it changes its structure from an object to array.
Also you must not mutate the original state values. The correct way to update is as below
const arrayInputHandler = (event, index, identifier) => {
const value = event.target.value;
setProfiles(prev => ({
...prev,
controls: profiles.controls.map((controls, i) => {
if(i === index) {
return {
...controls, [identifier]: {
...controls[identifier],
value
}
}
}
return controls
});
}));
};
P.S. You can always solve your problem in a simplistic manner like
const arrayInputHandler = (event, index, identifier) => {
const list = [...profiles.controls];
list[index][identifier] = event.target.value;
setProfiles({profile:list});
};
However its not a correct approach and should be avoided as react relies a lot on immutability for a lot of its re-rendering and other optimizations
I'm stuck in a thunk function usage and I need to better understand how it works.
Code that DOES NOT WORK:
const fields = {
id: {
type: new GraphQLNonNull(GraphQLID),
resolve: (obj) => dbIdToNodeId(obj._id, "History")
},
timestamp: {
type: GraphQLLong
},
objectId: {
type: GraphQLString
},
user: {
type: require('../User/connections').default,
args: connectionArgs,
resolve: (source, args) => {
return UserModel.findOne({ id: source.id }).exec();
}
},
action: {
type: new GraphQLNonNull(GraphQLString)
}
};
export const HistoryType = new GraphQLObjectType({
name: 'History',
description: 'History',
interfaces: () => [NodeInterface],
isTypeOf: (value) => value instanceof HistoryModel,
fields: () => (fields)
});
Code that DOES WORK:
export const HistoryType = new GraphQLObjectType({
name: 'History',
description: 'History',
interfaces: () => [NodeInterface],
isTypeOf: (value) => value instanceof HistoryModel,
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLID),
resolve: (obj) => dbIdToNodeId(obj._id, "History")
},
timestamp: {
type: GraphQLLong
},
objectId: {
type: GraphQLString
},
user: {
type: require('../User/connections').default,
args: connectionArgs,
resolve: (source, args) => {
return UserModel.findOne({ id: source.id }).exec();
}
},
action: {
type: new GraphQLNonNull(GraphQLString)
}
})
});
From my "not very skilled on JavaScript thunks" point of view, both codes are the same, but they are not as only the second one works, so:
a) Why the codes does not behave the same ? Why storing the thunk content in a function (fields) makes it behave differently ?
b) My fields variable is used in the sequence of the code, so is there a way to make the thunk work keeping its object in a separate variable, as code 1 (in fact a fix for code 1 that keeps the field variable) ?
My node definitions looks like this:
class Store {}
let store = new Store()
let nodeDefs = nodeDefinitions(
(globalId) => {
let type = fromGlobalId(globalId).type
let id = fromGlobalId(globalId).id
if (type === 'Store') {
return store
}
if (type === 'Video') {
return docClient.query(
Object.assign(
{},
{TableName: videosTable},
{KeyConditionExpression: 'id = :id'},
{ExpressionAttributeValues: { ':id': id }}
)
).promise().then(dataToConnection)
}
return null
},
(obj) => {
if (obj instanceof Store) {
return storeType
}
if (obj instanceof Video) {
return videoType
}
return null
}
)
The problem is that video node is always null, even when actual video is being returned from the database, because for it to not be null I need to look it up based on id or somehow fetch it from database.
This is the video node I am referring to:
video: {
type: videoType,
args: Object.assign(
{},
connectionArgs,
{id: {type: GraphQLString}}
),
resolve: (_, args) => {
return docClient.query(
Object.assign(
{},
{TableName: pokemonTable},
{KeyConditionExpression: 'id = :id'},
{ExpressionAttributeValues: { ':id': args.id }},
paginationToParams(args)
)
).promise().then(dataToConnection)
}
},
and
const videoType = new GraphQLObjectType({
name: 'Video',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLID),
resolve: (obj) => obj.id
},
name: { type: GraphQLString },
url: { type: GraphQLString }
}),
interfaces: [nodeDefs.nodeInterface]
})
const allVideosConnection = connectionDefinitions({
name: 'Video',
nodeType: videoType
})
I tried doing database query directly inside node definitions, but that didn't work.
dataToConnection just converts the output of dynamoDB:
video DATA!! { Items:
[ { id: 'f4623d92-3b48-4e1a-bfcc-01ff3c8cf754',
url: 'http://www.pokkentournament.com/assets/img/characters/char-detail/detail-pikachuLibre.png',
name: 'YAHOO' } ],
Count: 1,
ScannedCount: 1 }
into something that graphql relay understands:
video dataToConnection!! { edges:
[ { cursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
node: [Object] } ],
pageInfo:
{ startCursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
endCursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
hasPreviousPage: false,
hasNextPage: false } }
and the function itself can be found here: https://github.com/dowjones/graphql-dynamodb-connections/pull/3/files
It could be the problem.
Also, asking/querying for id makes the whole video object null:
But omitting id from the query returns something, whether querying with relay id:
or database id
and querying for all of the videos works:
The interesting part is that I get exactly same problem even if I delete the video part from node definitions:
let nodeDefs = nodeDefinitions(
(globalId) => {
let type = fromGlobalId(globalId).type
let id = fromGlobalId(globalId).id
if (type === 'Store') {
return store
}
return null
},
(obj) => {
if (obj instanceof Store) {
return storeType
}
return null
}
)
Any ideas?
UPDATE:
I did some digging and found that interfaces in fact is undefined
const storeType = new GraphQLObjectType({
name: 'Store',
fields: () => ({
id: globalIdField('Store'),
allVideosConnection: {
type: allVideosConnection.connectionType,
args: Object.assign(
{},
connectionArgs
),
resolve: (_, args) => {
return docClient.scan(
Object.assign(
{},
{TableName: pokemonTable},
paginationToParams(args)
)
).promise().then(dataToConnection)
}
},
video: {
type: videoType,
args: Object.assign(
{},
connectionArgs,
{id: {type: GraphQLString}}
),
resolve: (_, args) => {
return docClient.query(
Object.assign(
{},
{TableName: pokemonTable},
{KeyConditionExpression: 'id = :id'},
{ExpressionAttributeValues: { ':id': args.id }},
paginationToParams(args)
)
).promise().then(dataToConnection)
}
}
}),
interfaces: [nodeDefs.nodeInterface]
})
console.dir(storeType.interfaces, { depth: null })
prints undefined
Why? I clearly define them at the top!
Also, I can do that:
But this doesn't work:
This is what is being returned in video: {} resolve:
{ edges:
[ { cursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
node:
{ id: 'f4623d92-3b48-4e1a-bfcc-01ff3c8cf754',
url: 'http://www.pokkentournament.com/assets/img/characters/char-detail/detail-pikachuLibre.png',
name: 'YAHOO' } } ],
pageInfo:
{ startCursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
endCursor: 'ZHluYW1vZGJjb25uZWN0aW9uOmY0NjIzZDkyLTNiNDgtNGUxYS1iZmNjLTAxZmYzYzhjZjc1NA==',
hasPreviousPage: false,
hasNextPage: false } }
Somehow that's okay for allVideosConnection, but not okay (ends up null) for video
Do I need to convert ids of nodes to global IDs? using toGlobalId ? Just for video ?
Because another thing I noticed is that if I
console.log('fromGlobalId', fromGlobalId(globalId))
inside my node definitions, this query:
{
node(id: "f4623d92-3b48-4e1a-bfcc-01ff3c8cf754") {
id
...F1
}
}
fragment F1 on Video {
url
name
}
becomes this:
fromGlobalId { type: '', id: '\u000e6]_v{vxsn\u001eU/\u001b}G>SW_]O\u001c>x' }
However, if I do
I get
globalId U3RvcmU6
fromGlobalId { type: 'Store', id: '' }
So to make node definitions work, all I had to do was this:
class Video {}
let video = new Video()
return Object.assign(video, data.Items[0])
i.e. create class with the same name as type name
and then Object.assign to it
Just doing this, doesn't work:
return {Video: data.Items[0]}
I also need to create IDs in the database like that: Video:f4623d92-3b48-4e1a-bfcc-01ff3c8cf754, where I am essentially putting type and randomly generated unique id together separated by a colon (:) and then encode it with toGlobalId function of graphql-relay-js library (so I end up with VmlkZW86ZjQ2MjNkOTItM2I0OC00ZTFhLWJmY2MtMDFmZjNjOGNmNzU0Og==), so then I can decode it with fromGlobalId so that node definitions can retrieve both type and id({ type: 'Video', id: 'f4623d92-3b48-4e1a-bfcc-01ff3c8cf754:' }), after which I still need to add fromGlobalId(globalId).id.replace(/\:$/, '')) to remove the trailing colon (:).
`
Also, interfaces are not meant to be accessible, they are just for configuration.