linemanjs / lineman

Lineman helps you build fat-client JavaScript apps. It produces happiness by building assets, mocking servers, running specs on every file change
MIT License
1.18k stars 83 forks source link

n00b question about watch #265

Closed malcolmalex closed 10 years ago

malcolmalex commented 10 years ago

I'm very new with lineman, but finding it great. A question, though ... I am trying to convert to a directory structure where the unit tests (*_test.coffee) are right alongside the code to be tested more along the lines of the google angular project structure recommendation, using the following kind of structure in my files.coffee file:

  coffee:
    app: [
      "app/**/*.coffee"
      "!app/**/*_test.coffee"
    ]
    spec: "app/**/*_test.coffee"

But watch is not including the *_test.coffee files, even though my watch says says ...

  "coffeeSpecs": {
    "files": [
      "<%= files.coffee.specHelpers %>",
      "<%= files.coffee.spec %>"
    ],

It's like the files for watch get all put together, then the "app/*_/__test.coffee" gets removed from the list. Am I missing something or is there a better way to accomplish this? Thanks.

davemo commented 10 years ago

Hi @malcolmalex, you'll need to update the watch targets to account for the folder structure changes you want to make. To see what they look like you can run lineman config from the command line and it will spit out a formatted Object that showcases the entire config object.

If you want to narrow it down you can traverse the individual keys, like: lineman config watch

The section you'll need to modify is watch.coffee, the default values look like this:

$ lineman config watch.coffee
{
  "files": "<%= files.coffee.app %>",
  "tasks": [
    "coffee",
    "concat_sourcemap:js"
  ]
}

You can see there's an internal reference there to files.coffee.app, the default for that looks like this:

$ lineman config files.coffee
{
  "app": "app/js/**/*.coffee",
  "spec": "spec/**/*.coffee",
  "specHelpers": "spec/helpers/**/*.coffee",
  "generated": "generated/js/app.coffee.js",
  "generatedSpec": "generated/js/spec.coffee.js",
  "generatedSpecHelpers": "generated/js/spec-helpers.coffee.js"
}

The files.coffee.app key there is what you'll need to update to account for your changes.

malcolmalex commented 10 years ago

Thanks @davemo - I've been using lineman config .. heavily and it's helpful. Perhaps I'm going about it wrong ... I've tried to put the *_test.coffe in the files.coffee.spec key. So here's what I've got:

> lineman config files.coffee.app
[
  "app/**/*.coffee",
  "!app/**/*_test.coffee"
]

and

> lineman config files.coffee.spec
app/**/*_test.coffee

With my watch set up like ...

> lineman config watch.coffee
{
  "files": "<%= files.coffee.app %>",
  "tasks": [
    "coffee",
    "concat_sourcemap:js"
  ]
}

and

> lineman config watch.coffeeSpecs
{
  "files": [
    "<%= files.coffee.specHelpers %>",
    "<%= files.coffee.spec %>"
  ],
  "tasks": [
    "coffee",
    "concat_sourcemap:spec"
  ]
}

But ... the 'app/*_/__test.spec' files are not getting picked up in the watch. Is it possible that the full watch list is put together by concatenating all the watch arrays, then the "!..." stuff is removed - rather than taking each array, processing the "!..." on them individually, and then assembling the results?

davemo commented 10 years ago

So with files.coffee.app set to ignore *_test.coffee files the watch target won't trigger changes there. I think you'll want to override files.coffee in config/files.js like so:

{
    coffee: { 
        "spec" : "app/**/*_test.coffee"
    }
}

This will setup the watch.coffeeSpecs to point to the right file references located inside your app dir.

davemo commented 10 years ago

Hmm, I re-read what you posted and see you do have files.coffee.spec pointing to the right spot, this may be an issue with the watch task not handling the ! paths properly..

davemo commented 10 years ago

One other tip @malcolmalex, you can expand the interpolated strings using lineman config --process, this will help debug with the output of all matched paths being displayed :)

davemo commented 10 years ago

And yet another option, is to be able to see the actual file paths matched by globs is to use the -v flag with lineman run -v. Between those two improvements I think you should be able to debug it hopefully :)

malcolmalex commented 10 years ago

That is a helpful option. Running each of these with this option gives me what I would expect:

> lineman config watch.coffee --process
{
  "files": [
    "app/**/*.coffee",
    "!app/**/*_test.coffee"
  ],
  "tasks": [
    "coffee",
    "concat_sourcemap:js"
  ]
}

and

> lineman config watch.coffeeSpecs --process
{
  "files": [
    "spec/helpers/**/*.coffee",
    "app/**/*_test.coffee"
  ],
  "tasks": [
    "coffee",
    "concat_sourcemap:spec"
  ]
}

I wonder if it's related, but I just realized I'm getting some tasks executing twice - watch executes and then I get concat_sourcemap ... I will try and create a smaller project to duplicate and file separately if it's not a known issue.

malcolmalex commented 10 years ago

Gotcha - ok, I'll try the -v option and see what I can find out and comment back.

davemo commented 10 years ago

FYI, I realized there's an additional feature to lineman config that would be nice to have, something like a lineman config --expand flag that would both process the interpolations and expand globs, so I opened #266

malcolmalex commented 10 years ago

I think my initial suspicion was on track - lineman's watch is using grunt-watch-nospawn, which is using gaze, which is using fileset. In gaze the various arrays of file patterns get unioned. It is scanned to build an "include" and an "exclude" array, and then fileset is used to create a final array and all instances of "exclude" files are removed... which is why my !app/**/*_test.coffee are removed...

In gaze.js:

...
  this._patterns = union(this._patterns, files);

  var include = [], exclude = [];
  this._patterns.forEach(function(p) {
    if (p.slice(0, 1) === '!') {
      exclude.push(p.slice(1));
    } else {
      include.push(p);
    }
  });

  fileset(include, exclude, _this.options, function(err, files) {
    if (err) {
      _this.emit('error', err);
      return done(err);
    }
    _this._addToWatched(files);
    _this.close(false);
    _this._initWatched(done);
  });
};

and in fileset the files are processed one-by-one and excludes are thrown out ... fileset.js

Makes it difficult to co-locate the files like this!

malcolmalex commented 10 years ago

I think my solution for this in the angular context is going to be to search for specific patterns in files.app ... "app/*/{Controller,Directive,Service,Whatever}.html. I'm going to close out this issue. Thanks