#include <iostream>
#include <complex>
#include <vector>
#include <algorithm>
using namespace std;

class UnionFind
{
public:
		vector<int> data;
		UnionFind(int size):data(size, -1){}

		bool unionSet(int x, int y)
		{
			x = root(x); y=root(y);
			if(x != y)
			{
				if(data[y] < data[x]) swap(x, y);
				data[x] += data[y]; data[y] = x;
			}
			return x != y;
		}

		bool findSet(int x, int y)
		{
			return root(x) == root(y);
		}

		int root(int x)
		{
			return data[x] < 0 ? x:data[x]=root(data[x]);
		}
};

#define EPS 1e-8

typedef complex<double> P;
typedef vector<P> G;

struct L : public vector<P> {
  L(const P &a, const P &b) {
    push_back(a); push_back(b);
  }
};

double dot(const P& a, const P& b)
{
	return real(conj(a)*b);
}
double cross(const P& a, const P& b)
{
	return imag(conj(a)*b);
}

int ccw(P a, P b, P c)
{
	b -= a; c -= a;
	if (cross(b, c) > +EPS) return +1;
	if (cross(b, c) < -EPS) return -1;
	if (dot(b, c)   < -EPS) return +2;
	if (norm(b) < norm(c))  return -2;
	return 0;
}

bool intersectSS(const L &s, const L &t) {
  return ccw(s[0],s[1],t[0])*ccw(s[0],s[1],t[1]) <= 0 &&
         ccw(t[0],t[1],s[0])*ccw(t[0],t[1],s[1]) <= 0;
}

bool intersectSP(const L &s, const P &p) {
  return abs(s[0]-p)+abs(s[1]-p)-abs(s[1]-s[0]) < EPS; // triangle inequality
}
int isIntoG(const G &r, const P &p) {
  const int n = r.size();
  P g = (r[0] + r[n/3] + r[2*n/3]) / 3.0; // inner-point
  int a = 0, b = n;
  while (a+1 < b) { // invariant: c is in fan g-P[a]-P[b]
    int c = (a + b) / 2;
    if (cross(r[a]-g, r[c]-g) > 0) { // angle < 180 deg
      if (cross(r[a]-g, p-g) > 0 && cross(r[c]-g, p-g) < 0) b = c;
      else                                                  a = c;
    } else {
      if (cross(r[a]-g, p-g) < 0 && cross(r[c]-g, p-g) > 0) a = c;
      else                                                  b = c;
    }
  }
  b %= n;
  if (cross(r[a] - p, r[b] - p) < 0) return 0;
  if (cross(r[a] - p, r[b] - p) > 0) return 2;
  return 1;
}

bool isInto(const G& l, const G& r)
{
	for(int i=0; i<r.size(); i++)
		if(isIntoG(l, r[i])) return true;
	return false;
}


bool intersect(const G l, const G r)
{
	for(int i=0; i<l.size(); i++)
	for(int j=0; j<r.size(); j++)
	{
		if(intersectSS(L(l[i], l[(i+1)%l.size()]), L(r[j], r[(j+1)%r.size()]))) return true;
		if(abs(l[i]-r[j]) < EPS) return true;
		if(intersectSP(L(l[i], l[(i+1)%l.size()]), r[j])) return true;
		if(intersectSP(L(r[j], r[(j+1)%r.size()]), l[i])) return true;
	}
	return false;
}

bool isConnect(const G l, G r)
{
	return (intersect(l,r)||isInto(l,r)||isInto(r,l));
}

int main()
{
	int N,M;
	while(cin >> N, N)
	{
		while(N--)
		{
			cin >> M;
			vector<G> rect;
			while(M--)
			{
				G t;
				for(int i=0; i<4; i++)
				{
					int x,y;
					cin >> x >> y;
					t.push_back(P(x,y));
				}
				reverse(t.begin(), t.end());
				rect.push_back(t);
			}
			
			UnionFind uf(rect.size());
			for(int i=0; i<rect.size(); i++)
			for(int j=i+1; j<rect.size(); j++)
				if(isConnect(rect[i], rect[j])) uf.unionSet(i,j);
				
			int ret=0;
			bool checked[100]={0};
			for(int i=0; i<rect.size(); i++)
			{
				if(checked[i]) continue;
				checked[i]=1;
				ret++;
				for(int j=0; j<rect.size(); j++)
					if(uf.findSet(i,j)) checked[j]=1;
			}
			
			cout << ret << endl;
		}
	}
}