Giter Site home page Giter Site logo

Comments (26)

lukeed avatar lukeed commented on July 22, 2024 3

https://github.com/terkelg/prompts#injectvalues

from prompts.

ianwalter avatar ianwalter commented on July 22, 2024 2

How about you only wipe the matched key/answer on completion? That way you could write this before testing myUI:

prompts.inject({ q1: 'a1', q2: 'a1' })

And then when q1 resolves, it wipes q1. When q2 resolves, it wipes q2.

Also note from my example that I personally would want to just inject 1 object per inject call. Passing multiple objects seems arbitrary and cumbersome when the answer keys are matched to the question names and not the prompt index anyway.

from prompts.

terkelg avatar terkelg commented on July 22, 2024 1

I guess we can do this in prompt.js without any modifications to the actual prompt classes. That would be ideal

from prompts.

lukeed avatar lukeed commented on July 22, 2024 1

That would be kinda pointless, no? Names have to be unique otherwise prompts overwrite the values. We'd just need to test a 1:1 mapping so that we get expected output.

from prompts.

terkelg avatar terkelg commented on July 22, 2024

Thanks a lot @simonepri. I agree and prompts itself need better tests for sure. Let me have a look at this

from prompts.

simonepri avatar simonepri commented on July 22, 2024

The idea is to add an API that enables to prepare the answers for the following prompts.
If I prepare 2 prompts then the next 2 prompts will not show the UI and they simply resolves with the answers given before.

Some more examples:

const prompts = require('prompts');
prompt.prepare({
  meaning: '42'
});
prompts.prepare({
  text: 'Some text'
});
let response1 = await prompts({
    type: 'text',
    name: 'meaning',
    message: 'What is the meaning of life?'
});
let response2 = await prompts({
    type: 'text',
    name: 'text',
    message: 'Insert some text?'
});
let response3 = await prompts({
    type: 'text',
    name: 'color',
    message: 'What is your favourite color?'
});
console.log(response1); // => '42'
console.log(response2); // => 'Some text'
console.log(response3); // => This should be answered manually

This is just an idea, let me know if you have better way to achieve this.

from prompts.

terkelg avatar terkelg commented on July 22, 2024

Right, so they shouldn't render at all and immediately resolve when there's a prepare method defined?

from prompts.

lukeed avatar lukeed commented on July 22, 2024

I'd personally prefer it be called inject -- not that it really matters.

Since it's defined first, could save to an internal map if answers. Each prompt will look for its answer by name from the map. If not found it proceeds with the UX/prompt.

from prompts.

lukeed avatar lukeed commented on July 22, 2024

The naming suggestion alludes more strongly to the fact that you're taking over/hijacking the responses, rather than preparing a set of defaults.

from prompts.

simonepri avatar simonepri commented on July 22, 2024

Yeah it makes sense, no preference over the name. inject sound cool.

Each prompt will look for its answer by name from the map.

We actually need to store an array of maps. Then each prompts pop from that array.

Example scenario where a single map wouldn't be enough.

const prompts = require('prompts');
prompt.inject({
  meaning: '42'
});
prompts.inject({
  meaning: '24'
});
let response1 = await prompts({
    type: 'text',
    name: 'meaning',
    message: 'What is the answer to life the universe and everything?'
});
let response2 = await prompts({
    type: 'text',
    name: 'meaning',
    message: 'What is the meaning of life?'
});
console.log(response1); // => '42'
console.log(response2); // => '24'

from prompts.

simonepri avatar simonepri commented on July 22, 2024

This is an example of implementation:

function inject(answers) {
  injections.concat(answers);
}
function prompt(questions) {
  if (injections.lenght > 0) {
    const answers = injections.pop();
    // Use the answers
    // If there isn't a default and there isn't an answer for a particular question then throw an exception
  } else {
    // Use the UI
  }
}

from prompts.

simonepri avatar simonepri commented on July 22, 2024

That would be kinda pointless, no?

No, because maybe you have a block of code that you want to test, that internally creates 2+ prompts chains with some common names.

Example:

async function toTest() {
  let questions = [{
    type: 'text',
    name: 'username',
    message: 'What is your GitHub username?'
  }, {
    type: 'age',
    name: 'age',
    message: 'How old are you?'
  }, {
    type: 'text',
    name: 'about',
    message: 'Tell somethign about yourself',
    initial: 'Why should I?'
  }];
  let answers1 = await prompts(questions);
  let answers2 = await prompts(questions);
  return [answers1, answers2];
}

Then you want to be able to do this:

prompts.inject({username: 'simonepri', age: -1, about: 'Something'});
prompts.inject({username: 'lukeed', age: -1});
[answers1, answers2] = await toTest();
// Check if the answer are the one expected

from prompts.

terkelg avatar terkelg commented on July 22, 2024

What do you think @lukeed?

from prompts.

lukeed avatar lukeed commented on July 22, 2024

I'm used to testing individual instances since it's more fine-grained control. Also synchronous code is easier to debug, especially in tests. Also, internally, it becomes simpler to look up in a single map/object, rather than traversing the list & making sure that everything is flushed at the correct times.

Here's my ideal usage, wherein calling prompts force-wipes the internal mapping on completion (hence the re-definitions).

const prompts = require('prompts');

let dynamics = [
  {
    name: 'like',
    type: 'boolean',
    message: 'Do you like pizza?'
  }, {
    name: 'topping',
    type: bool => bool && 'text',
    message: 'Name a topping'
  }
];
prompts.inject({ like:false, topping:'none' });
let foo = await prompts(dynamics);
//=> { like:false }

prompts.inject({ like:true, topping:'pineapple' });
let bar = await prompts(dynamics);
//=> { like:true, topping:'pineapple' }



let chained = [
  {
    type: 'text',
    name: 'username',
    message: 'What is your GitHub username?'
  }, {
    type: 'age',
    name: 'age',
    message: 'How old are you?'
  }, {
    type: 'text',
    name: 'about',
    message: 'Tell somethign about yourself',
    initial: 'Why should I?'
  }
];

prompts.inject({ username:'lukeed', age:100, about:'Something' });
let foo = await prompts(chained);
//=> { username:'lukeed', age:100, about:'Something' }

// with `onSubmit` callback
prompts.inject({ username:'lukeed', age:120, about:'Something' });
let bar = await prompts(chained, {
  // stop if age > 100
  onSubmit: (obj, val) => obj.name==='age' && val > 100
});
//=> { username:'lukeed', age:120 }

from prompts.

simonepri avatar simonepri commented on July 22, 2024

How do you test the example I've provided where you "can't" modify toTest but you want to inject both?
The approach of having an array of maps is just more flexible and with it your ideal usage just works.

from prompts.

lukeed avatar lukeed commented on July 22, 2024

I'm not sure that I understand why toTest has to be like that? I mean, among other things, that function is kinda point as is. It feels very much like an edge case & probably shouldn't be the example on which to build out a testing solution.

It requires changes, but something like this is far more useful:

function toTest(extra=[]) {
  let questions = [{ type:'text', ... }, {...}].concat(extra);
  return prompts(questions);
}

prompts.inject({ answers:123 });
let answers1 = await toTest();

prompts.inject({ foo:123, bar:456, baz:'hello' });
let answers2 = await toTest([
  {
    type: 'text',
    name: 'username',
    message: 'What is your GitHub username?'
  }, {
    type: 'age',
    name: 'age',
    message: 'How old are you?'
  }
]);

Again, it's just my opinion & isn't up to me. I just feel that one-off injections are easier to reason about, implement, and (therefore) test.

from prompts.

terkelg avatar terkelg commented on July 22, 2024

I learn more towards @lukeed. What about adding a third param or option could that fix both cases?

from prompts.

simonepri avatar simonepri commented on July 22, 2024

Yes it is an edge case for sure.
I think that was come to my mind because we have two different ways to view this API

Your:

  prompts.inject({q1: 'a1', q2: 'a2', q3: 'a3'});  // one single object for all prompts
  let q1 = await prompts({
    type: 'text',
    name: 'q1',
    message: 'Question 1'
  });
  let q2 = await prompts({
    type: 'text',
    name: 'q2',
    message: 'Question 2'
  }, {
    type: 'text',
    name: 'q3',
    message: 'Question 3'
  });

Mine:

  prompts.inject({q1: 'a1'}, {q2: 'a2', q3: 'a3'}); // one object per prompt
  let q1 = await prompts({
    type: 'text',
    name: 'q1',
    message: 'Question 1'
  });
  let q2 = await prompts({
    type: 'text',
    name: 'q2',
    message: 'Question 2'
  }, {
    type: 'text',
    name: 'q3',
    message: 'Question 3'
  });

That said, I have no specific needs or preferences, is just another point of view.
But I agree that your approach seems easier

from prompts.

lukeed avatar lukeed commented on July 22, 2024

Yeah, for sure!

Quick clarification, mine is actually one inject per prompts(), as prompts() would wipe the internal map on completion. So mine would look like this:

  prompts.inject({q1: 'a1' });
  let q1 = await prompts({
    type: 'text',
    name: 'q1',
    message: 'Question 1'
  });

  prompts.inject({ q2: 'a2', q3: 'a3'});
  let q2 = await prompts({
    type: 'text',
    name: 'q2',
    message: 'Question 2'
  }, {
    type: 'text',
    name: 'q3',
    message: 'Question 3'
  });

from prompts.

simonepri avatar simonepri commented on July 22, 2024

Yes but there are a lot of cases where you can't wipe on completion:

Imagine the following situation:

// q1.js
async function q1() {
  return prompts({
    type: 'text',
    name: 'q1',
    message: 'Question 1'
  });
}

// q2.js
async function q2() {
  return prompts({
    type: 'text',
    name: 'q2',
    message: 'Question 2'
  });
}

// ui.js
const q1 = require('q1.js');
const q2 = require('q2.js');
function myUI() {
  return 'The answers are: (' + await q1 + ', ' + await q2 + ')';
}

Now you can test q1.js and q2.js but you can't test ui.js if you wipe on completion.

Then with the array approach a simple:
prompts.inject({q1: 'a1'}, {q2: 'a2'});
lets you test also ui.js.

Obviously you can re-code the example to use one single prompt, but as said I'm just imagining all the scenarios.

from prompts.

lukeed avatar lukeed commented on July 22, 2024

That'd be a good balance, and I don't think it'd be easy to leak accidentally.

from prompts.

terkelg avatar terkelg commented on July 22, 2024

Right! I might implement this tonight. Just to be sure I get it right:

  1. In the prompt loop we look up the internal "inject object".
  2. If a matching value is found resolve with that value immediately and delete the property from the inject object. Otherwise continue and leave the rest of the values alone for future prompts?
  3. Every time you call prompts.inject it adds to the same internal inject object

from prompts.

lukeed avatar lukeed commented on July 22, 2024

Yep! Something like

inject(obj) {
  this._map = this._map || {};
  for (let k in obj) {
    this._map[k] = obj[k];
  }
}

// ...

  let answer, quit, name;
  let map = this._map || {};

  for (const prompt of prompts) {
    name = prompt.name;
    
    if (map[name] !== void 0) {
      answers[name] = map[name];
      delete map[name];
      continue;
    }

    // if property is a function, invoke it unless it's ignored
    // ...
  }

  return answers;

from prompts.

simonepri avatar simonepri commented on July 22, 2024

Shouldn't be for (let k of obj)?

from prompts.

lukeed avatar lukeed commented on July 22, 2024

No, single object input. Still sticking to my preference 😄

from prompts.

sashaaKr avatar sashaaKr commented on July 22, 2024

Was looking for how to test prompts and found this issue,
it seems that this functionality is not documented as far as I see, but I think it deserve to be on readme and have dedicated example for simple test, WDYT?
If yes, I will be happy to contribute.

from prompts.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.